|
1 // Copyright 2009 the V8 project authors. All rights reserved. |
|
2 // Redistribution and use in source and binary forms, with or without |
|
3 // modification, are permitted provided that the following conditions are |
|
4 // met: |
|
5 // |
|
6 // * Redistributions of source code must retain the above copyright |
|
7 // notice, this list of conditions and the following disclaimer. |
|
8 // * Redistributions in binary form must reproduce the above |
|
9 // copyright notice, this list of conditions and the following |
|
10 // disclaimer in the documentation and/or other materials provided |
|
11 // with the distribution. |
|
12 // * Neither the name of Google Inc. nor the names of its |
|
13 // contributors may be used to endorse or promote products derived |
|
14 // from this software without specific prior written permission. |
|
15 // |
|
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
27 |
|
28 // This benchmark is based on a JavaScript log processing module used |
|
29 // by the V8 profiler to generate execution time profiles for runs of |
|
30 // JavaScript applications, and it effectively measures how fast the |
|
31 // JavaScript engine is at allocating nodes and reclaiming the memory |
|
32 // used for old nodes. Because of the way splay trees work, the engine |
|
33 // also has to deal with a lot of changes to the large tree object |
|
34 // graph. |
|
35 |
|
36 //var Splay = new BenchmarkSuite('Splay', 126125, [ |
|
37 // new Benchmark("Splay", SplayRun, SplaySetup, SplayTearDown) |
|
38 //]); |
|
39 |
|
40 // This is the best random number generator available to mankind ;) |
|
41 var MyMath = { |
|
42 seed: 49734321, |
|
43 random: function() { |
|
44 // Robert Jenkins' 32 bit integer hash function. |
|
45 this.seed = ((this.seed + 0x7ed55d16) + (this.seed << 12)) & 0xffffffff; |
|
46 this.seed = ((this.seed ^ 0xc761c23c) ^ (this.seed >>> 19)) & 0xffffffff; |
|
47 this.seed = ((this.seed + 0x165667b1) + (this.seed << 5)) & 0xffffffff; |
|
48 this.seed = ((this.seed + 0xd3a2646c) ^ (this.seed << 9)) & 0xffffffff; |
|
49 this.seed = ((this.seed + 0xfd7046c5) + (this.seed << 3)) & 0xffffffff; |
|
50 this.seed = ((this.seed ^ 0xb55a4f09) ^ (this.seed >>> 16)) & 0xffffffff; |
|
51 return (this.seed & 0xfffffff) / 0x10000000; |
|
52 }, |
|
53 }; |
|
54 |
|
55 // Configuration. |
|
56 var kSplayTreeSize = 8000; |
|
57 var kSplayTreeModifications = 80; |
|
58 var kSplayTreePayloadDepth = 5; |
|
59 |
|
60 var splayTree = null; |
|
61 |
|
62 |
|
63 function GeneratePayloadTree(depth, key) { |
|
64 if (depth == 0) { |
|
65 return { |
|
66 array : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], |
|
67 string : 'String for key ' + key + ' in leaf node' |
|
68 }; |
|
69 } else { |
|
70 return { |
|
71 left: GeneratePayloadTree(depth - 1, key), |
|
72 right: GeneratePayloadTree(depth - 1, key) |
|
73 }; |
|
74 } |
|
75 } |
|
76 |
|
77 |
|
78 function GenerateKey() { |
|
79 // The benchmark framework guarantees that Math.random is |
|
80 // deterministic; see base.js. |
|
81 // base.js isn't pulled in for jit-tests |
|
82 return MyMath.random(); |
|
83 } |
|
84 |
|
85 |
|
86 function InsertNewNode() { |
|
87 // Insert new node with a unique key. |
|
88 var key; |
|
89 do { |
|
90 key = GenerateKey(); |
|
91 } while (splayTree.find(key) != null); |
|
92 splayTree.insert(key, GeneratePayloadTree(kSplayTreePayloadDepth, key)); |
|
93 return key; |
|
94 } |
|
95 |
|
96 |
|
97 function SplaySetup() { |
|
98 splayTree = new SplayTree(); |
|
99 for (var i = 0; i < kSplayTreeSize; i++) InsertNewNode(); |
|
100 } |
|
101 |
|
102 |
|
103 function SplayTearDown() { |
|
104 // Allow the garbage collector to reclaim the memory |
|
105 // used by the splay tree no matter how we exit the |
|
106 // tear down function. |
|
107 var keys = splayTree.exportKeys(); |
|
108 splayTree = null; |
|
109 |
|
110 // Verify that the splay tree has the right size. |
|
111 var length = keys.length; |
|
112 assertEq(length, kSplayTreeSize); |
|
113 |
|
114 // Verify that the splay tree has sorted, unique keys. |
|
115 for (var i = 0; i < length - 1; i++) { |
|
116 assertEq(keys[i] < keys[i + 1], true); |
|
117 } |
|
118 } |
|
119 |
|
120 |
|
121 function SplayRun() { |
|
122 // Replace a few nodes in the splay tree. |
|
123 for (var i = 0; i < kSplayTreeModifications; i++) { |
|
124 var key = InsertNewNode(); |
|
125 var greatest = splayTree.findGreatestLessThan(key); |
|
126 if (greatest == null) splayTree.remove(key); |
|
127 else splayTree.remove(greatest.key); |
|
128 } |
|
129 } |
|
130 |
|
131 |
|
132 /** |
|
133 * Constructs a Splay tree. A splay tree is a self-balancing binary |
|
134 * search tree with the additional property that recently accessed |
|
135 * elements are quick to access again. It performs basic operations |
|
136 * such as insertion, look-up and removal in O(log(n)) amortized time. |
|
137 * |
|
138 * @constructor |
|
139 */ |
|
140 function SplayTree() { |
|
141 }; |
|
142 |
|
143 |
|
144 /** |
|
145 * Pointer to the root node of the tree. |
|
146 * |
|
147 * @type {SplayTree.Node} |
|
148 * @private |
|
149 */ |
|
150 SplayTree.prototype.root_ = null; |
|
151 |
|
152 |
|
153 /** |
|
154 * @return {boolean} Whether the tree is empty. |
|
155 */ |
|
156 SplayTree.prototype.isEmpty = function() { |
|
157 return !this.root_; |
|
158 }; |
|
159 |
|
160 |
|
161 /** |
|
162 * Inserts a node into the tree with the specified key and value if |
|
163 * the tree does not already contain a node with the specified key. If |
|
164 * the value is inserted, it becomes the root of the tree. |
|
165 * |
|
166 * @param {number} key Key to insert into the tree. |
|
167 * @param {*} value Value to insert into the tree. |
|
168 */ |
|
169 SplayTree.prototype.insert = function(key, value) { |
|
170 if (this.isEmpty()) { |
|
171 this.root_ = new SplayTree.Node(key, value); |
|
172 return; |
|
173 } |
|
174 // Splay on the key to move the last node on the search path for |
|
175 // the key to the root of the tree. |
|
176 this.splay_(key); |
|
177 if (this.root_.key == key) { |
|
178 return; |
|
179 } |
|
180 var node = new SplayTree.Node(key, value); |
|
181 if (key > this.root_.key) { |
|
182 node.left = this.root_; |
|
183 node.right = this.root_.right; |
|
184 this.root_.right = null; |
|
185 } else { |
|
186 node.right = this.root_; |
|
187 node.left = this.root_.left; |
|
188 this.root_.left = null; |
|
189 } |
|
190 this.root_ = node; |
|
191 }; |
|
192 |
|
193 |
|
194 /** |
|
195 * Removes a node with the specified key from the tree if the tree |
|
196 * contains a node with this key. The removed node is returned. If the |
|
197 * key is not found, an exception is thrown. |
|
198 * |
|
199 * @param {number} key Key to find and remove from the tree. |
|
200 * @return {SplayTree.Node} The removed node. |
|
201 */ |
|
202 SplayTree.prototype.remove = function(key) { |
|
203 if (this.isEmpty()) { |
|
204 throw Error('Key not found: ' + key); |
|
205 } |
|
206 this.splay_(key); |
|
207 if (this.root_.key != key) { |
|
208 throw Error('Key not found: ' + key); |
|
209 } |
|
210 var removed = this.root_; |
|
211 if (!this.root_.left) { |
|
212 this.root_ = this.root_.right; |
|
213 } else { |
|
214 var right = this.root_.right; |
|
215 this.root_ = this.root_.left; |
|
216 // Splay to make sure that the new root has an empty right child. |
|
217 this.splay_(key); |
|
218 // Insert the original right child as the right child of the new |
|
219 // root. |
|
220 this.root_.right = right; |
|
221 } |
|
222 return removed; |
|
223 }; |
|
224 |
|
225 |
|
226 /** |
|
227 * Returns the node having the specified key or null if the tree doesn't contain |
|
228 * a node with the specified key. |
|
229 * |
|
230 * @param {number} key Key to find in the tree. |
|
231 * @return {SplayTree.Node} Node having the specified key. |
|
232 */ |
|
233 SplayTree.prototype.find = function(key) { |
|
234 if (this.isEmpty()) { |
|
235 return null; |
|
236 } |
|
237 this.splay_(key); |
|
238 return this.root_.key == key ? this.root_ : null; |
|
239 }; |
|
240 |
|
241 |
|
242 /** |
|
243 * @return {SplayTree.Node} Node having the maximum key value that |
|
244 * is less or equal to the specified key value. |
|
245 */ |
|
246 SplayTree.prototype.findGreatestLessThan = function(key) { |
|
247 if (this.isEmpty()) { |
|
248 return null; |
|
249 } |
|
250 // Splay on the key to move the node with the given key or the last |
|
251 // node on the search path to the top of the tree. |
|
252 this.splay_(key); |
|
253 // Now the result is either the root node or the greatest node in |
|
254 // the left subtree. |
|
255 if (this.root_.key <= key) { |
|
256 return this.root_; |
|
257 } else if (this.root_.left) { |
|
258 return this.findMax(this.root_.left); |
|
259 } else { |
|
260 return null; |
|
261 } |
|
262 }; |
|
263 |
|
264 |
|
265 /** |
|
266 * @return {Array<*>} An array containing all the keys of tree's nodes. |
|
267 */ |
|
268 SplayTree.prototype.exportKeys = function() { |
|
269 var result = []; |
|
270 if (!this.isEmpty()) { |
|
271 this.root_.traverse_(function(node) { result.push(node.key); }); |
|
272 } |
|
273 return result; |
|
274 }; |
|
275 |
|
276 |
|
277 /** |
|
278 * Perform the splay operation for the given key. Moves the node with |
|
279 * the given key to the top of the tree. If no node has the given |
|
280 * key, the last node on the search path is moved to the top of the |
|
281 * tree. This is the simplified top-down splaying algorithm from: |
|
282 * "Self-adjusting Binary Search Trees" by Sleator and Tarjan |
|
283 * |
|
284 * @param {number} key Key to splay the tree on. |
|
285 * @private |
|
286 */ |
|
287 SplayTree.prototype.splay_ = function(key) { |
|
288 if (this.isEmpty()) { |
|
289 return; |
|
290 } |
|
291 // Create a dummy node. The use of the dummy node is a bit |
|
292 // counter-intuitive: The right child of the dummy node will hold |
|
293 // the L tree of the algorithm. The left child of the dummy node |
|
294 // will hold the R tree of the algorithm. Using a dummy node, left |
|
295 // and right will always be nodes and we avoid special cases. |
|
296 var dummy, left, right; |
|
297 dummy = left = right = new SplayTree.Node(null, null); |
|
298 var current = this.root_; |
|
299 while (true) { |
|
300 if (key < current.key) { |
|
301 if (!current.left) { |
|
302 break; |
|
303 } |
|
304 if (key < current.left.key) { |
|
305 // Rotate right. |
|
306 var tmp = current.left; |
|
307 current.left = tmp.right; |
|
308 tmp.right = current; |
|
309 current = tmp; |
|
310 if (!current.left) { |
|
311 break; |
|
312 } |
|
313 } |
|
314 // Link right. |
|
315 right.left = current; |
|
316 right = current; |
|
317 current = current.left; |
|
318 } else if (key > current.key) { |
|
319 if (!current.right) { |
|
320 break; |
|
321 } |
|
322 if (key > current.right.key) { |
|
323 // Rotate left. |
|
324 var tmp = current.right; |
|
325 current.right = tmp.left; |
|
326 tmp.left = current; |
|
327 current = tmp; |
|
328 if (!current.right) { |
|
329 break; |
|
330 } |
|
331 } |
|
332 // Link left. |
|
333 left.right = current; |
|
334 left = current; |
|
335 current = current.right; |
|
336 } else { |
|
337 break; |
|
338 } |
|
339 } |
|
340 // Assemble. |
|
341 left.right = current.left; |
|
342 right.left = current.right; |
|
343 current.left = dummy.right; |
|
344 current.right = dummy.left; |
|
345 this.root_ = current; |
|
346 }; |
|
347 |
|
348 |
|
349 /** |
|
350 * Constructs a Splay tree node. |
|
351 * |
|
352 * @param {number} key Key. |
|
353 * @param {*} value Value. |
|
354 */ |
|
355 SplayTree.Node = function(key, value) { |
|
356 this.key = key; |
|
357 this.value = value; |
|
358 }; |
|
359 |
|
360 |
|
361 /** |
|
362 * @type {SplayTree.Node} |
|
363 */ |
|
364 SplayTree.Node.prototype.left = null; |
|
365 |
|
366 |
|
367 /** |
|
368 * @type {SplayTree.Node} |
|
369 */ |
|
370 SplayTree.Node.prototype.right = null; |
|
371 |
|
372 |
|
373 /** |
|
374 * Performs an ordered traversal of the subtree starting at |
|
375 * this SplayTree.Node. |
|
376 * |
|
377 * @param {function(SplayTree.Node)} f Visitor function. |
|
378 * @private |
|
379 */ |
|
380 SplayTree.Node.prototype.traverse_ = function(f) { |
|
381 var current = this; |
|
382 while (current) { |
|
383 var left = current.left; |
|
384 if (left) left.traverse_(f); |
|
385 f(current); |
|
386 current = current.right; |
|
387 } |
|
388 }; |
|
389 |
|
390 SplaySetup(); |
|
391 SplayRun(); |
|
392 SplayTearDown(); |