|
| 1 | +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 | +// for details. All rights reserved. Use of this source code is governed by a |
| 3 | +// BSD-style license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +// This benchmark is based on a JavaScript log processing module used |
| 6 | +// by the V8 profiler to generate execution time profiles for runs of |
| 7 | +// JavaScript applications, and it effectively measures how fast the |
| 8 | +// JavaScript engine is at allocating nodes and reclaiming the memory |
| 9 | +// used for old nodes. Because of the way splay trees work, the engine |
| 10 | +// also has to deal with a lot of changes to the large tree object |
| 11 | +// graph. |
| 12 | + |
| 13 | +// VMOptions= |
| 14 | +// VMOptions=--no_concurrent_mark --no_concurrent_sweep |
| 15 | +// VMOptions=--no_concurrent_mark --concurrent_sweep |
| 16 | +// VMOptions=--no_concurrent_mark --use_compactor |
| 17 | +// VMOptions=--no_concurrent_mark --use_compactor --force_evacuation |
| 18 | +// VMOptions=--concurrent_mark --no_concurrent_sweep |
| 19 | +// VMOptions=--concurrent_mark --concurrent_sweep |
| 20 | +// VMOptions=--concurrent_mark --use_compactor |
| 21 | +// VMOptions=--concurrent_mark --use_compactor --force_evacuation |
| 22 | +// VMOptions=--verify_before_gc |
| 23 | +// VMOptions=--verify_after_gc |
| 24 | +// VMOptions=--verify_before_gc --verify_after_gc |
| 25 | +// VMOptions=--verify_store_buffer |
| 26 | + |
| 27 | +import "dart:math"; |
| 28 | +import 'package:benchmark_harness/benchmark_harness.dart'; |
| 29 | + |
| 30 | +void main() { |
| 31 | + Splay.main(); |
| 32 | +} |
| 33 | + |
| 34 | +class Splay extends BenchmarkBase { |
| 35 | + const Splay() : super("Splay"); |
| 36 | + |
| 37 | + // Configuration. |
| 38 | + static final int kTreeSize = 8000; |
| 39 | + static final int kTreeModifications = 80; |
| 40 | + static final int kTreePayloadDepth = 5; |
| 41 | + |
| 42 | + static SplayTree tree; |
| 43 | + |
| 44 | + static Random rnd = new Random(12345); |
| 45 | + |
| 46 | + // Insert new node with a unique key. |
| 47 | + static num insertNewNode() { |
| 48 | + num key; |
| 49 | + do { |
| 50 | + key = rnd.nextDouble(); |
| 51 | + } while (tree.find(key) != null); |
| 52 | + Payload payload = Payload.generate(kTreePayloadDepth, key.toString()); |
| 53 | + tree.insert(key, payload); |
| 54 | + return key; |
| 55 | + } |
| 56 | + |
| 57 | + static void mysetup() { |
| 58 | + tree = new SplayTree(); |
| 59 | + for (int i = 0; i < kTreeSize; i++) insertNewNode(); |
| 60 | + } |
| 61 | + |
| 62 | + static void tearDown() { |
| 63 | + // Allow the garbage collector to reclaim the memory |
| 64 | + // used by the splay tree no matter how we exit the |
| 65 | + // tear down function. |
| 66 | + List<num> keys = tree.exportKeys(); |
| 67 | + tree = null; |
| 68 | + |
| 69 | + // Verify that the splay tree has the right size. |
| 70 | + int length = keys.length; |
| 71 | + if (length != kTreeSize) throw new Error("Splay tree has wrong size"); |
| 72 | + |
| 73 | + // Verify that the splay tree has sorted, unique keys. |
| 74 | + for (int i = 0; i < length - 1; i++) { |
| 75 | + if (keys[i] >= keys[i + 1]) throw new Error("Splay tree not sorted"); |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | + void warmup() { |
| 80 | + exercise(); |
| 81 | + } |
| 82 | + |
| 83 | + void exercise() { |
| 84 | + // Replace a few nodes in the splay tree. |
| 85 | + for (int i = 0; i < kTreeModifications; i++) { |
| 86 | + num key = insertNewNode(); |
| 87 | + Node greatest = tree.findGreatestLessThan(key); |
| 88 | + if (greatest == null) tree.remove(key); |
| 89 | + else tree.remove(greatest.key); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + static void main() { |
| 94 | + mysetup(); |
| 95 | + new Splay().report(); |
| 96 | + tearDown(); |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | + |
| 101 | +class Leaf { |
| 102 | + Leaf(String tag) { |
| 103 | + string = "String for key $tag in leaf node"; |
| 104 | + array = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; |
| 105 | + } |
| 106 | + String string; |
| 107 | + List<num> array; |
| 108 | +} |
| 109 | + |
| 110 | + |
| 111 | +class Payload { |
| 112 | + Payload(this.left, this.right); |
| 113 | + var left, right; |
| 114 | + |
| 115 | + static generate(depth, tag) { |
| 116 | + if (depth == 0) return new Leaf(tag); |
| 117 | + return new Payload(generate(depth - 1, tag), |
| 118 | + generate(depth - 1, tag)); |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +class Error implements Exception { |
| 123 | + const Error(this.message); |
| 124 | + final String message; |
| 125 | +} |
| 126 | + |
| 127 | + |
| 128 | +/** |
| 129 | + * A splay tree is a self-balancing binary search tree with the additional |
| 130 | + * property that recently accessed elements are quick to access again. |
| 131 | + * It performs basic operations such as insertion, look-up and removal |
| 132 | + * in O(log(n)) amortized time. |
| 133 | + */ |
| 134 | +class SplayTree { |
| 135 | + SplayTree(); |
| 136 | + |
| 137 | + /** |
| 138 | + * Inserts a node into the tree with the specified [key] and value if |
| 139 | + * the tree does not already contain a node with the specified key. If |
| 140 | + * the value is inserted, it becomes the root of the tree. |
| 141 | + */ |
| 142 | + void insert(num key, value) { |
| 143 | + if (isEmpty) { |
| 144 | + root = new Node(key, value); |
| 145 | + return; |
| 146 | + } |
| 147 | + // Splay on the key to move the last node on the search path for |
| 148 | + // the key to the root of the tree. |
| 149 | + splay(key); |
| 150 | + if (root.key == key) return; |
| 151 | + Node node = new Node(key, value); |
| 152 | + if (key > root.key) { |
| 153 | + node.left = root; |
| 154 | + node.right = root.right; |
| 155 | + root.right = null; |
| 156 | + } else { |
| 157 | + node.right = root; |
| 158 | + node.left = root.left; |
| 159 | + root.left = null; |
| 160 | + } |
| 161 | + root = node; |
| 162 | + } |
| 163 | + |
| 164 | + /** |
| 165 | + * Removes a node with the specified key from the tree if the tree |
| 166 | + * contains a node with this key. The removed node is returned. If |
| 167 | + * [key] is not found, an exception is thrown. |
| 168 | + */ |
| 169 | + Node remove(num key) { |
| 170 | + if (isEmpty) throw new Error('Key not found: $key'); |
| 171 | + splay(key); |
| 172 | + if (root.key != key) throw new Error('Key not found: $key'); |
| 173 | + Node removed = root; |
| 174 | + if (root.left == null) { |
| 175 | + root = root.right; |
| 176 | + } else { |
| 177 | + Node right = root.right; |
| 178 | + root = root.left; |
| 179 | + // Splay to make sure that the new root has an empty right child. |
| 180 | + splay(key); |
| 181 | + // Insert the original right child as the right child of the new |
| 182 | + // root. |
| 183 | + root.right = right; |
| 184 | + } |
| 185 | + return removed; |
| 186 | + } |
| 187 | + |
| 188 | + /** |
| 189 | + * Returns the node having the specified [key] or null if the tree doesn't |
| 190 | + * contain a node with the specified [key]. |
| 191 | + */ |
| 192 | + Node find(num key) { |
| 193 | + if (isEmpty) return null; |
| 194 | + splay(key); |
| 195 | + return root.key == key ? root : null; |
| 196 | + } |
| 197 | + |
| 198 | + /** |
| 199 | + * Returns the Node having the maximum key value. |
| 200 | + */ |
| 201 | + Node findMax([Node start]) { |
| 202 | + if (isEmpty) return null; |
| 203 | + Node current = null == start ? root : start; |
| 204 | + while (current.right != null) current = current.right; |
| 205 | + return current; |
| 206 | + } |
| 207 | + |
| 208 | + /** |
| 209 | + * Returns the Node having the maximum key value that |
| 210 | + * is less than the specified [key]. |
| 211 | + */ |
| 212 | + Node findGreatestLessThan(num key) { |
| 213 | + if (isEmpty) return null; |
| 214 | + // Splay on the key to move the node with the given key or the last |
| 215 | + // node on the search path to the top of the tree. |
| 216 | + splay(key); |
| 217 | + // Now the result is either the root node or the greatest node in |
| 218 | + // the left subtree. |
| 219 | + if (root.key < key) return root; |
| 220 | + if (root.left != null) return findMax(root.left); |
| 221 | + return null; |
| 222 | + } |
| 223 | + |
| 224 | + /** |
| 225 | + * Perform the splay operation for the given key. Moves the node with |
| 226 | + * the given key to the top of the tree. If no node has the given |
| 227 | + * key, the last node on the search path is moved to the top of the |
| 228 | + * tree. This is the simplified top-down splaying algorithm from: |
| 229 | + * "Self-adjusting Binary Search Trees" by Sleator and Tarjan |
| 230 | + */ |
| 231 | + void splay(num key) { |
| 232 | + if (isEmpty) return; |
| 233 | + // Create a dummy node. The use of the dummy node is a bit |
| 234 | + // counter-intuitive: The right child of the dummy node will hold |
| 235 | + // the L tree of the algorithm. The left child of the dummy node |
| 236 | + // will hold the R tree of the algorithm. Using a dummy node, left |
| 237 | + // and right will always be nodes and we avoid special cases. |
| 238 | + final Node dummy = new Node(null, null); |
| 239 | + Node left = dummy; |
| 240 | + Node right = dummy; |
| 241 | + Node current = root; |
| 242 | + while (true) { |
| 243 | + if (key < current.key) { |
| 244 | + if (current.left == null) break; |
| 245 | + if (key < current.left.key) { |
| 246 | + // Rotate right. |
| 247 | + Node tmp = current.left; |
| 248 | + current.left = tmp.right; |
| 249 | + tmp.right = current; |
| 250 | + current = tmp; |
| 251 | + if (current.left == null) break; |
| 252 | + } |
| 253 | + // Link right. |
| 254 | + right.left = current; |
| 255 | + right = current; |
| 256 | + current = current.left; |
| 257 | + } else if (key > current.key) { |
| 258 | + if (current.right == null) break; |
| 259 | + if (key > current.right.key) { |
| 260 | + // Rotate left. |
| 261 | + Node tmp = current.right; |
| 262 | + current.right = tmp.left; |
| 263 | + tmp.left = current; |
| 264 | + current = tmp; |
| 265 | + if (current.right == null) break; |
| 266 | + } |
| 267 | + // Link left. |
| 268 | + left.right = current; |
| 269 | + left = current; |
| 270 | + current = current.right; |
| 271 | + } else { |
| 272 | + break; |
| 273 | + } |
| 274 | + } |
| 275 | + // Assemble. |
| 276 | + left.right = current.left; |
| 277 | + right.left = current.right; |
| 278 | + current.left = dummy.right; |
| 279 | + current.right = dummy.left; |
| 280 | + root = current; |
| 281 | + } |
| 282 | + |
| 283 | + /** |
| 284 | + * Returns a list with all the keys of the tree. |
| 285 | + */ |
| 286 | + List<num> exportKeys() { |
| 287 | + List<num> result = []; |
| 288 | + if (!isEmpty) root.traverse((Node node) => result.add(node.key)); |
| 289 | + return result; |
| 290 | + } |
| 291 | + |
| 292 | + // Tells whether the tree is empty. |
| 293 | + bool get isEmpty => null == root; |
| 294 | + |
| 295 | + // Pointer to the root node of the tree. |
| 296 | + Node root; |
| 297 | +} |
| 298 | + |
| 299 | + |
| 300 | +class Node { |
| 301 | + Node(this.key, this.value); |
| 302 | + final num key; |
| 303 | + final Object value; |
| 304 | + |
| 305 | + Node left, right; |
| 306 | + |
| 307 | + /** |
| 308 | + * Performs an ordered traversal of the subtree starting here. |
| 309 | + */ |
| 310 | + void traverse(void f(Node n)) { |
| 311 | + Node current = this; |
| 312 | + while (current != null) { |
| 313 | + Node left = current.left; |
| 314 | + if (left != null) left.traverse(f); |
| 315 | + f(current); |
| 316 | + current = current.right; |
| 317 | + } |
| 318 | + } |
| 319 | +} |
0 commit comments