|
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', 81491, [ |
|
37 new Benchmark("Splay", SplayRun, SplaySetup, SplayTearDown) |
|
38 ]); |
|
39 |
|
40 |
|
41 // Configuration. |
|
42 var kSplayTreeSize = 8000; |
|
43 var kSplayTreeModifications = 80; |
|
44 var kSplayTreePayloadDepth = 5; |
|
45 |
|
46 var splayTree = null; |
|
47 |
|
48 |
|
49 function GeneratePayloadTree(depth, tag) { |
|
50 if (depth == 0) { |
|
51 return { |
|
52 array : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], |
|
53 string : 'String for key ' + tag + ' in leaf node' |
|
54 }; |
|
55 } else { |
|
56 return { |
|
57 left: GeneratePayloadTree(depth - 1, tag), |
|
58 right: GeneratePayloadTree(depth - 1, tag) |
|
59 }; |
|
60 } |
|
61 } |
|
62 |
|
63 |
|
64 function GenerateKey() { |
|
65 // The benchmark framework guarantees that Math.random is |
|
66 // deterministic; see base.js. |
|
67 return Math.random(); |
|
68 } |
|
69 |
|
70 |
|
71 function InsertNewNode() { |
|
72 // Insert new node with a unique key. |
|
73 var key; |
|
74 do { |
|
75 key = GenerateKey(); |
|
76 } while (splayTree.find(key) != null); |
|
77 var payload = GeneratePayloadTree(kSplayTreePayloadDepth, String(key)); |
|
78 splayTree.insert(key, payload); |
|
79 return key; |
|
80 } |
|
81 |
|
82 |
|
83 |
|
84 function SplaySetup() { |
|
85 splayTree = new SplayTree(); |
|
86 for (var i = 0; i < kSplayTreeSize; i++) InsertNewNode(); |
|
87 } |
|
88 |
|
89 |
|
90 function SplayTearDown() { |
|
91 // Allow the garbage collector to reclaim the memory |
|
92 // used by the splay tree no matter how we exit the |
|
93 // tear down function. |
|
94 var keys = splayTree.exportKeys(); |
|
95 splayTree = null; |
|
96 |
|
97 // Verify that the splay tree has the right size. |
|
98 var length = keys.length; |
|
99 if (length != kSplayTreeSize) { |
|
100 throw new Error("Splay tree has wrong size"); |
|
101 } |
|
102 |
|
103 // Verify that the splay tree has sorted, unique keys. |
|
104 for (var i = 0; i < length - 1; i++) { |
|
105 if (keys[i] >= keys[i + 1]) { |
|
106 throw new Error("Splay tree not sorted"); |
|
107 } |
|
108 } |
|
109 } |
|
110 |
|
111 |
|
112 function SplayRun() { |
|
113 // Replace a few nodes in the splay tree. |
|
114 for (var i = 0; i < kSplayTreeModifications; i++) { |
|
115 var key = InsertNewNode(); |
|
116 var greatest = splayTree.findGreatestLessThan(key); |
|
117 if (greatest == null) splayTree.remove(key); |
|
118 else splayTree.remove(greatest.key); |
|
119 } |
|
120 } |
|
121 |
|
122 |
|
123 /** |
|
124 * Constructs a Splay tree. A splay tree is a self-balancing binary |
|
125 * search tree with the additional property that recently accessed |
|
126 * elements are quick to access again. It performs basic operations |
|
127 * such as insertion, look-up and removal in O(log(n)) amortized time. |
|
128 * |
|
129 * @constructor |
|
130 */ |
|
131 function SplayTree() { |
|
132 }; |
|
133 |
|
134 |
|
135 /** |
|
136 * Pointer to the root node of the tree. |
|
137 * |
|
138 * @type {SplayTree.Node} |
|
139 * @private |
|
140 */ |
|
141 SplayTree.prototype.root_ = null; |
|
142 |
|
143 |
|
144 /** |
|
145 * @return {boolean} Whether the tree is empty. |
|
146 */ |
|
147 SplayTree.prototype.isEmpty = function() { |
|
148 return !this.root_; |
|
149 }; |
|
150 |
|
151 |
|
152 /** |
|
153 * Inserts a node into the tree with the specified key and value if |
|
154 * the tree does not already contain a node with the specified key. If |
|
155 * the value is inserted, it becomes the root of the tree. |
|
156 * |
|
157 * @param {number} key Key to insert into the tree. |
|
158 * @param {*} value Value to insert into the tree. |
|
159 */ |
|
160 SplayTree.prototype.insert = function(key, value) { |
|
161 if (this.isEmpty()) { |
|
162 this.root_ = new SplayTree.Node(key, value); |
|
163 return; |
|
164 } |
|
165 // Splay on the key to move the last node on the search path for |
|
166 // the key to the root of the tree. |
|
167 this.splay_(key); |
|
168 if (this.root_.key == key) { |
|
169 return; |
|
170 } |
|
171 var node = new SplayTree.Node(key, value); |
|
172 if (key > this.root_.key) { |
|
173 node.left = this.root_; |
|
174 node.right = this.root_.right; |
|
175 this.root_.right = null; |
|
176 } else { |
|
177 node.right = this.root_; |
|
178 node.left = this.root_.left; |
|
179 this.root_.left = null; |
|
180 } |
|
181 this.root_ = node; |
|
182 }; |
|
183 |
|
184 |
|
185 /** |
|
186 * Removes a node with the specified key from the tree if the tree |
|
187 * contains a node with this key. The removed node is returned. If the |
|
188 * key is not found, an exception is thrown. |
|
189 * |
|
190 * @param {number} key Key to find and remove from the tree. |
|
191 * @return {SplayTree.Node} The removed node. |
|
192 */ |
|
193 SplayTree.prototype.remove = function(key) { |
|
194 if (this.isEmpty()) { |
|
195 throw Error('Key not found: ' + key); |
|
196 } |
|
197 this.splay_(key); |
|
198 if (this.root_.key != key) { |
|
199 throw Error('Key not found: ' + key); |
|
200 } |
|
201 var removed = this.root_; |
|
202 if (!this.root_.left) { |
|
203 this.root_ = this.root_.right; |
|
204 } else { |
|
205 var right = this.root_.right; |
|
206 this.root_ = this.root_.left; |
|
207 // Splay to make sure that the new root has an empty right child. |
|
208 this.splay_(key); |
|
209 // Insert the original right child as the right child of the new |
|
210 // root. |
|
211 this.root_.right = right; |
|
212 } |
|
213 return removed; |
|
214 }; |
|
215 |
|
216 |
|
217 /** |
|
218 * Returns the node having the specified key or null if the tree doesn't contain |
|
219 * a node with the specified key. |
|
220 * |
|
221 * @param {number} key Key to find in the tree. |
|
222 * @return {SplayTree.Node} Node having the specified key. |
|
223 */ |
|
224 SplayTree.prototype.find = function(key) { |
|
225 if (this.isEmpty()) { |
|
226 return null; |
|
227 } |
|
228 this.splay_(key); |
|
229 return this.root_.key == key ? this.root_ : null; |
|
230 }; |
|
231 |
|
232 |
|
233 /** |
|
234 * @return {SplayTree.Node} Node having the maximum key value. |
|
235 */ |
|
236 SplayTree.prototype.findMax = function(opt_startNode) { |
|
237 if (this.isEmpty()) { |
|
238 return null; |
|
239 } |
|
240 var current = opt_startNode || this.root_; |
|
241 while (current.right) { |
|
242 current = current.right; |
|
243 } |
|
244 return current; |
|
245 }; |
|
246 |
|
247 |
|
248 /** |
|
249 * @return {SplayTree.Node} Node having the maximum key value that |
|
250 * is less than the specified key value. |
|
251 */ |
|
252 SplayTree.prototype.findGreatestLessThan = function(key) { |
|
253 if (this.isEmpty()) { |
|
254 return null; |
|
255 } |
|
256 // Splay on the key to move the node with the given key or the last |
|
257 // node on the search path to the top of the tree. |
|
258 this.splay_(key); |
|
259 // Now the result is either the root node or the greatest node in |
|
260 // the left subtree. |
|
261 if (this.root_.key < key) { |
|
262 return this.root_; |
|
263 } else if (this.root_.left) { |
|
264 return this.findMax(this.root_.left); |
|
265 } else { |
|
266 return null; |
|
267 } |
|
268 }; |
|
269 |
|
270 |
|
271 /** |
|
272 * @return {Array<*>} An array containing all the keys of tree's nodes. |
|
273 */ |
|
274 SplayTree.prototype.exportKeys = function() { |
|
275 var result = []; |
|
276 if (!this.isEmpty()) { |
|
277 this.root_.traverse_(function(node) { result.push(node.key); }); |
|
278 } |
|
279 return result; |
|
280 }; |
|
281 |
|
282 |
|
283 /** |
|
284 * Perform the splay operation for the given key. Moves the node with |
|
285 * the given key to the top of the tree. If no node has the given |
|
286 * key, the last node on the search path is moved to the top of the |
|
287 * tree. This is the simplified top-down splaying algorithm from: |
|
288 * "Self-adjusting Binary Search Trees" by Sleator and Tarjan |
|
289 * |
|
290 * @param {number} key Key to splay the tree on. |
|
291 * @private |
|
292 */ |
|
293 SplayTree.prototype.splay_ = function(key) { |
|
294 if (this.isEmpty()) { |
|
295 return; |
|
296 } |
|
297 // Create a dummy node. The use of the dummy node is a bit |
|
298 // counter-intuitive: The right child of the dummy node will hold |
|
299 // the L tree of the algorithm. The left child of the dummy node |
|
300 // will hold the R tree of the algorithm. Using a dummy node, left |
|
301 // and right will always be nodes and we avoid special cases. |
|
302 var dummy, left, right; |
|
303 dummy = left = right = new SplayTree.Node(null, null); |
|
304 var current = this.root_; |
|
305 while (true) { |
|
306 if (key < current.key) { |
|
307 if (!current.left) { |
|
308 break; |
|
309 } |
|
310 if (key < current.left.key) { |
|
311 // Rotate right. |
|
312 var tmp = current.left; |
|
313 current.left = tmp.right; |
|
314 tmp.right = current; |
|
315 current = tmp; |
|
316 if (!current.left) { |
|
317 break; |
|
318 } |
|
319 } |
|
320 // Link right. |
|
321 right.left = current; |
|
322 right = current; |
|
323 current = current.left; |
|
324 } else if (key > current.key) { |
|
325 if (!current.right) { |
|
326 break; |
|
327 } |
|
328 if (key > current.right.key) { |
|
329 // Rotate left. |
|
330 var tmp = current.right; |
|
331 current.right = tmp.left; |
|
332 tmp.left = current; |
|
333 current = tmp; |
|
334 if (!current.right) { |
|
335 break; |
|
336 } |
|
337 } |
|
338 // Link left. |
|
339 left.right = current; |
|
340 left = current; |
|
341 current = current.right; |
|
342 } else { |
|
343 break; |
|
344 } |
|
345 } |
|
346 // Assemble. |
|
347 left.right = current.left; |
|
348 right.left = current.right; |
|
349 current.left = dummy.right; |
|
350 current.right = dummy.left; |
|
351 this.root_ = current; |
|
352 }; |
|
353 |
|
354 |
|
355 /** |
|
356 * Constructs a Splay tree node. |
|
357 * |
|
358 * @param {number} key Key. |
|
359 * @param {*} value Value. |
|
360 */ |
|
361 SplayTree.Node = function(key, value) { |
|
362 this.key = key; |
|
363 this.value = value; |
|
364 }; |
|
365 |
|
366 |
|
367 /** |
|
368 * @type {SplayTree.Node} |
|
369 */ |
|
370 SplayTree.Node.prototype.left = null; |
|
371 |
|
372 |
|
373 /** |
|
374 * @type {SplayTree.Node} |
|
375 */ |
|
376 SplayTree.Node.prototype.right = null; |
|
377 |
|
378 |
|
379 /** |
|
380 * Performs an ordered traversal of the subtree starting at |
|
381 * this SplayTree.Node. |
|
382 * |
|
383 * @param {function(SplayTree.Node)} f Visitor function. |
|
384 * @private |
|
385 */ |
|
386 SplayTree.Node.prototype.traverse_ = function(f) { |
|
387 var current = this; |
|
388 while (current) { |
|
389 var left = current.left; |
|
390 if (left) left.traverse_(f); |
|
391 f(current); |
|
392 current = current.right; |
|
393 } |
|
394 }; |