Ben Murdoch | c561043 | 2016-08-08 18:44:38 +0100 | [diff] [blame^] | 1 | // Copyright 2016 the V8 project authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | function ObjectWithKeys(count, keyOffset = 0, keyGen) { |
| 6 | var body = ""; |
| 7 | for (var i = 0; i < count; i++) { |
| 8 | var key = keyGen(i + keyOffset); |
| 9 | if (typeof key === "string") { |
| 10 | body += `this.${key} = 0\n`; |
| 11 | } else { |
| 12 | body += `this[${key}] = 0\n`; |
| 13 | } |
| 14 | } |
| 15 | var f = new Function(body); |
| 16 | return new f(); |
| 17 | } |
| 18 | |
| 19 | function ObjectWithProperties(count, keyOffset) { |
| 20 | return ObjectWithKeys(count, keyOffset, (key) => "key" + key ); |
| 21 | } |
| 22 | |
| 23 | function ObjectWithElements(count, keyOffset) { |
| 24 | return ObjectWithKeys(count, keyOffset, (key) => key ); |
| 25 | } |
| 26 | |
| 27 | function ObjectWithMixedKeys(count, keyOffset) { |
| 28 | return ObjectWithKeys(count, keyOffset, (key) => { |
| 29 | if (key % 2 == 0) return (key / 2); |
| 30 | return "key" + ((key - 1) / 2); |
| 31 | }); |
| 32 | } |
| 33 | |
| 34 | // Create an object with #depth prototypes each having #keys properties |
| 35 | // generated by given keyGen. |
| 36 | function ObjectWithProtoKeys(depth, keys, cacheable, |
| 37 | keyGen = ObjectWithProperties) { |
| 38 | var o = keyGen(keys); |
| 39 | var current = o; |
| 40 | var keyOffset = 0; |
| 41 | for (var i = 0; i < depth; i++) { |
| 42 | keyOffset += keys; |
| 43 | current.__proto__ = keyGen(keys, keyOffset); |
| 44 | current = current.__proto__; |
| 45 | } |
| 46 | if (cacheable === false) { |
| 47 | // Add an empty proxy at the prototype chain to make caching properties |
| 48 | // impossible. |
| 49 | current.__proto__ = new Proxy({}, {}); |
| 50 | } |
| 51 | return o; |
| 52 | } |
| 53 | |
| 54 | |
| 55 | function HoleyIntArray(size) { |
| 56 | var array = new Array(size); |
| 57 | for (var i = 0; i < size; i += 3) { |
| 58 | array[i] = i; |
| 59 | } |
| 60 | return array |
| 61 | } |
| 62 | |
| 63 | function IntArray(size) { |
| 64 | var array = new Array(size); |
| 65 | for (var i = 0; i < size; i++) { |
| 66 | array[i] = i; |
| 67 | } |
| 68 | return array; |
| 69 | } |
| 70 | |
| 71 | // Switch object's properties and elements to dictionary mode. |
| 72 | function MakeDictionaryMode(obj) { |
| 73 | obj.foo = 0; |
| 74 | delete obj.foo; |
| 75 | obj[1e9] = 0; |
| 76 | return obj; |
| 77 | } |
| 78 | |
| 79 | function Internalize(s) { |
| 80 | return Object.keys({[s]:0})[0]; |
| 81 | } |
| 82 | |
| 83 | function Deinternalize(s) { |
| 84 | return [...s].join(""); |
| 85 | } |
| 86 | |
| 87 | // ============================================================================ |
| 88 | |
| 89 | const QUERY_INTERNALIZED_PROP = "INTERN-prop"; |
| 90 | const QUERY_DEINTERNALIZED_PROP = "DEINTERN-prop"; |
| 91 | const QUERY_NON_EXISTING_INTERNALIZED_PROP = "NE-INTERN-prop"; |
| 92 | const QUERY_NON_EXISTING_DEINTERNALIZED_PROP = "NE-DEINTERN-prop"; |
| 93 | const QUERY_ELEMENT = "el"; |
| 94 | const QUERY_ELEMENT_AS_STRING = "el-str"; |
| 95 | const QUERY_NON_EXISTING_ELEMENT = "NE-el"; |
| 96 | |
| 97 | const OBJ_MODE_FAST = "fast"; |
| 98 | const OBJ_MODE_SLOW = "slow"; |
| 99 | |
| 100 | var TestQueries = [ |
| 101 | QUERY_INTERNALIZED_PROP, |
| 102 | QUERY_DEINTERNALIZED_PROP, |
| 103 | QUERY_NON_EXISTING_INTERNALIZED_PROP, |
| 104 | QUERY_NON_EXISTING_DEINTERNALIZED_PROP, |
| 105 | QUERY_ELEMENT, |
| 106 | QUERY_ELEMENT_AS_STRING, |
| 107 | QUERY_NON_EXISTING_ELEMENT, |
| 108 | ]; |
| 109 | |
| 110 | const QUERIES_PER_OBJECT_NUMBER = 10; |
| 111 | |
| 112 | // Leave only every "count"th keys. |
| 113 | function FilterKeys(keys, count) { |
| 114 | var len = keys.length; |
| 115 | if (len < count) throw new Error("Keys array is too short: " + len); |
| 116 | var step = len / count; |
| 117 | if (step == 0) throw new Error("Bad count specified: " + count); |
| 118 | return keys.filter((element, index) => index % step == 0); |
| 119 | } |
| 120 | |
| 121 | |
| 122 | function MakeKeyQueries(keys, query_kind) { |
| 123 | var properties = keys.filter((element) => isNaN(Number(element))); |
| 124 | var elements = keys.filter((element) => !isNaN(Number(element))); |
| 125 | |
| 126 | properties = FilterKeys(properties, QUERIES_PER_OBJECT_NUMBER); |
| 127 | elements = FilterKeys(elements, QUERIES_PER_OBJECT_NUMBER); |
| 128 | |
| 129 | switch (query_kind) { |
| 130 | case QUERY_INTERNALIZED_PROP: |
| 131 | return properties; |
| 132 | |
| 133 | case QUERY_DEINTERNALIZED_PROP: |
| 134 | return properties.map(Deinternalize); |
| 135 | |
| 136 | case QUERY_NON_EXISTING_INTERNALIZED_PROP: |
| 137 | case QUERY_NON_EXISTING_DEINTERNALIZED_PROP: |
| 138 | var non_existing = []; |
| 139 | for (var i = 0; i < QUERIES_PER_OBJECT_NUMBER; i++) { |
| 140 | non_existing.push("non-existing" + i); |
| 141 | } |
| 142 | if (query_kind == QUERY_NON_EXISTING_INTERNALIZED_PROP) { |
| 143 | return non_existing.map(Internalize); |
| 144 | } else { |
| 145 | return non_existing.map(Deinternalize); |
| 146 | } |
| 147 | |
| 148 | case QUERY_ELEMENT: |
| 149 | return elements.map(Number); |
| 150 | |
| 151 | case QUERY_ELEMENT_AS_STRING: |
| 152 | return elements.map(String); |
| 153 | |
| 154 | case QUERY_NON_EXISTING_ELEMENT: |
| 155 | var non_existing = []; |
| 156 | for (var i = 0; i < QUERIES_PER_OBJECT_NUMBER; i++) { |
| 157 | non_existing.push(1200 + 100*i); |
| 158 | } |
| 159 | return non_existing; |
| 160 | |
| 161 | default: |
| 162 | throw new Error("Bad query_kind: " + query_kind); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | |
| 167 | var TestData = []; |
| 168 | |
| 169 | [true, false].forEach((cachable) => { |
| 170 | [OBJ_MODE_FAST, OBJ_MODE_SLOW].forEach((obj_mode) => { |
| 171 | var proto_mode = cachable ? "" : "-with-slow-proto"; |
| 172 | var name = `${obj_mode}-obj${proto_mode}`; |
| 173 | var objects = []; |
| 174 | [10, 50, 100, 200, 500].forEach((prop_count) => { |
| 175 | // Create object with prop_count properties and prop_count elements. |
| 176 | obj = ObjectWithProtoKeys(5, prop_count * 2, cachable, |
| 177 | ObjectWithMixedKeys); |
| 178 | if (obj_mode == OBJ_MODE_SLOW) { |
| 179 | obj = MakeDictionaryMode(obj); |
| 180 | } |
| 181 | objects.push(obj); |
| 182 | }); |
| 183 | TestData.push({name, objects}); |
| 184 | }); |
| 185 | }); |
| 186 | |
| 187 | |
| 188 | // ============================================================================ |
| 189 | |
| 190 | function CreateTestFunction(template, object, keys) { |
| 191 | // Force a new function for each test-object to avoid side-effects due to ICs. |
| 192 | var text = "// random comment " + Math.random() + "\n" + |
| 193 | template(object, keys); |
| 194 | var func = new Function("object", "keys", text); |
| 195 | return () => func(object, keys); |
| 196 | } |
| 197 | |
| 198 | function CombineTestFunctions(tests) { |
| 199 | return () => { |
| 200 | for (var i = 0; i < tests.length; i++ ) { |
| 201 | tests[i](); |
| 202 | } |
| 203 | }; |
| 204 | } |
| 205 | |
| 206 | var TestFunctions = [ |
| 207 | { |
| 208 | name: "in", |
| 209 | // Query all keys. |
| 210 | keys: (object) => Object.keys(object), |
| 211 | template: (object, keys) => { |
| 212 | var lines = [ |
| 213 | `var result = true;`, |
| 214 | `for (var i = 0; i < keys.length; i++) {`, |
| 215 | ` var key = keys[i];`, |
| 216 | ` result = (key in object) && result;`, |
| 217 | `}`, |
| 218 | `return result;`, |
| 219 | ]; |
| 220 | return lines.join("\n"); |
| 221 | }, |
| 222 | }, |
| 223 | { |
| 224 | name: "Object.hasOwnProperty", |
| 225 | // Query only own keys. |
| 226 | keys: (object) => Object.getOwnPropertyNames(object), |
| 227 | template: (object, keys) => { |
| 228 | var lines = [ |
| 229 | `var result = true;`, |
| 230 | `for (var i = 0; i < keys.length; i++) {`, |
| 231 | ` var key = keys[i];`, |
| 232 | ` result = object.hasOwnProperty(key) && result;`, |
| 233 | `}`, |
| 234 | `return result;`, |
| 235 | ]; |
| 236 | return lines.join("\n"); |
| 237 | }, |
| 238 | }, |
| 239 | ]; |
| 240 | |
| 241 | |
| 242 | // ============================================================================ |
| 243 | // Create the benchmark suites. We create a suite for each pair of the test |
| 244 | // functions above and query kind. Each suite contains benchmarks for each |
| 245 | // object type. |
| 246 | var Benchmarks = []; |
| 247 | |
| 248 | for (var test_function_desc of TestFunctions) { |
| 249 | var test_function_name = test_function_desc.name; |
| 250 | |
| 251 | for (var query_kind of TestQueries) { |
| 252 | var benchmarks = []; |
| 253 | var suit_name = test_function_name + "--" + query_kind; |
| 254 | for (var test_data of TestData) { |
| 255 | var name = suit_name + "--" + test_data.name; |
| 256 | |
| 257 | var tests = []; |
| 258 | for (var object of test_data.objects) { |
| 259 | var keys = test_function_desc.keys(object); |
| 260 | keys = MakeKeyQueries(keys, query_kind); |
| 261 | |
| 262 | var test = CreateTestFunction(test_function_desc.template, object, |
| 263 | keys); |
| 264 | tests.push(test); |
| 265 | } |
| 266 | var run_function = CombineTestFunctions(tests); |
| 267 | var benchmark = new Benchmark(name, false, false, 0, run_function); |
| 268 | benchmarks.push(benchmark); |
| 269 | } |
| 270 | Benchmarks.push(new BenchmarkSuite(suit_name, [100], benchmarks)); |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | // ============================================================================ |