Home | History | Annotate | Download | only in benchmarks
      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 };
    395