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