Home | History | Annotate | Download | only in v8-v6
      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.
     37 var kSplayTreeSize = 8000;
     38 var kSplayTreeModifications = 80;
     39 var kSplayTreePayloadDepth = 5;
     40 
     41 var splayTree = null;
     42 
     43 
     44 function 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 
     59 function GenerateKey() {
     60   // The benchmark framework guarantees that Math.random is
     61   // deterministic; see base.js.
     62   return Math.random();
     63 }
     64 
     65 
     66 function 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 
     79 function SplaySetup() {
     80   splayTree = new SplayTree();
     81   for (var i = 0; i < kSplayTreeSize; i++) InsertNewNode();
     82 }
     83 
     84 
     85 function 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 
    107 function 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  */
    126 function SplayTree() {
    127 };
    128 
    129 
    130 /**
    131  * Pointer to the root node of the tree.
    132  *
    133  * @type {SplayTree.Node}
    134  * @private
    135  */
    136 SplayTree.prototype.root_ = null;
    137 
    138 
    139 /**
    140  * @return {boolean} Whether the tree is empty.
    141  */
    142 SplayTree.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  */
    155 SplayTree.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  */
    188 SplayTree.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  */
    219 SplayTree.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  */
    231 SplayTree.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  */
    247 SplayTree.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  */
    269 SplayTree.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  */
    288 SplayTree.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  */
    356 SplayTree.Node = function(key, value) {
    357   this.key = key;
    358   this.value = value;
    359 };
    360 
    361 
    362 /**
    363  * @type {SplayTree.Node}
    364  */
    365 SplayTree.Node.prototype.left = null;
    366 
    367 
    368 /**
    369  * @type {SplayTree.Node}
    370  */
    371 SplayTree.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  */
    381 SplayTree.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 
    391 SplaySetup();
    392 SplayRun();
    393 SplayTearDown();
    394