| // Copyright 2016 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| function ObjectWithKeys(count, keyOffset = 0, keyGen) { |
| var body = ""; |
| for (var i = 0; i < count; i++) { |
| var key = keyGen(i + keyOffset); |
| if (typeof key === "string") { |
| body += `this.${key} = 0\n`; |
| } else { |
| body += `this[${key}] = 0\n`; |
| } |
| } |
| var f = new Function(body); |
| return new f(); |
| } |
| |
| function ObjectWithProperties(count, keyOffset) { |
| return ObjectWithKeys(count, keyOffset, (key) => "key" + key ); |
| } |
| |
| function ObjectWithElements(count, keyOffset) { |
| return ObjectWithKeys(count, keyOffset, (key) => key ); |
| } |
| |
| function ObjectWithMixedKeys(count, keyOffset) { |
| return ObjectWithKeys(count, keyOffset, (key) => { |
| if (key % 2 == 0) return (key / 2); |
| return "key" + ((key - 1) / 2); |
| }); |
| } |
| |
| // Create an object with #depth prototypes each having #keys properties |
| // generated by given keyGen. |
| function ObjectWithProtoKeys(depth, keys, cacheable, |
| keyGen = ObjectWithProperties) { |
| var o = keyGen(keys); |
| var current = o; |
| var keyOffset = 0; |
| for (var i = 0; i < depth; i++) { |
| keyOffset += keys; |
| current.__proto__ = keyGen(keys, keyOffset); |
| current = current.__proto__; |
| } |
| if (cacheable === false) { |
| // Add an empty proxy at the prototype chain to make caching properties |
| // impossible. |
| current.__proto__ = new Proxy({}, {}); |
| } |
| return o; |
| } |
| |
| |
| function HoleyIntArray(size) { |
| var array = new Array(size); |
| for (var i = 0; i < size; i += 3) { |
| array[i] = i; |
| } |
| return array |
| } |
| |
| function IntArray(size) { |
| var array = new Array(size); |
| for (var i = 0; i < size; i++) { |
| array[i] = i; |
| } |
| return array; |
| } |
| |
| // Switch object's properties and elements to dictionary mode. |
| function MakeDictionaryMode(obj) { |
| obj.foo = 0; |
| obj.bar = 0; |
| // Delete the second-to-last property first to force normalization. |
| delete obj.foo; |
| delete obj.bar; |
| obj[1e9] = 0; |
| return obj; |
| } |
| |
| function Internalize(s) { |
| return Object.keys({[s]:0})[0]; |
| } |
| |
| function Deinternalize(s) { |
| return [...s].join(""); |
| } |
| |
| // ============================================================================ |
| |
| const QUERY_INTERNALIZED_PROP = "INTERN-prop"; |
| const QUERY_DEINTERNALIZED_PROP = "DEINTERN-prop"; |
| const QUERY_NON_EXISTING_INTERNALIZED_PROP = "NE-INTERN-prop"; |
| const QUERY_NON_EXISTING_DEINTERNALIZED_PROP = "NE-DEINTERN-prop"; |
| const QUERY_ELEMENT = "el"; |
| const QUERY_ELEMENT_AS_STRING = "el-str"; |
| const QUERY_NON_EXISTING_ELEMENT = "NE-el"; |
| |
| const OBJ_MODE_FAST = "fast"; |
| const OBJ_MODE_SLOW = "slow"; |
| |
| var TestQueries = [ |
| QUERY_INTERNALIZED_PROP, |
| QUERY_DEINTERNALIZED_PROP, |
| QUERY_NON_EXISTING_INTERNALIZED_PROP, |
| QUERY_NON_EXISTING_DEINTERNALIZED_PROP, |
| QUERY_ELEMENT, |
| QUERY_ELEMENT_AS_STRING, |
| QUERY_NON_EXISTING_ELEMENT, |
| ]; |
| |
| const QUERIES_PER_OBJECT_NUMBER = 10; |
| |
| // Leave only every "count"th keys. |
| function FilterKeys(keys, count) { |
| var len = keys.length; |
| if (len < count) throw new Error("Keys array is too short: " + len); |
| var step = len / count; |
| if (step == 0) throw new Error("Bad count specified: " + count); |
| return keys.filter((element, index) => index % step == 0); |
| } |
| |
| |
| function MakeKeyQueries(keys, query_kind) { |
| var properties = keys.filter((element) => isNaN(Number(element))); |
| var elements = keys.filter((element) => !isNaN(Number(element))); |
| |
| properties = FilterKeys(properties, QUERIES_PER_OBJECT_NUMBER); |
| elements = FilterKeys(elements, QUERIES_PER_OBJECT_NUMBER); |
| |
| switch (query_kind) { |
| case QUERY_INTERNALIZED_PROP: |
| return properties; |
| |
| case QUERY_DEINTERNALIZED_PROP: |
| return properties.map(Deinternalize); |
| |
| case QUERY_NON_EXISTING_INTERNALIZED_PROP: |
| case QUERY_NON_EXISTING_DEINTERNALIZED_PROP: |
| var non_existing = []; |
| for (var i = 0; i < QUERIES_PER_OBJECT_NUMBER; i++) { |
| non_existing.push("non-existing" + i); |
| } |
| if (query_kind == QUERY_NON_EXISTING_INTERNALIZED_PROP) { |
| return non_existing.map(Internalize); |
| } else { |
| return non_existing.map(Deinternalize); |
| } |
| |
| case QUERY_ELEMENT: |
| return elements.map(Number); |
| |
| case QUERY_ELEMENT_AS_STRING: |
| return elements.map(String); |
| |
| case QUERY_NON_EXISTING_ELEMENT: |
| var non_existing = []; |
| for (var i = 0; i < QUERIES_PER_OBJECT_NUMBER; i++) { |
| non_existing.push(1200 + 100*i); |
| } |
| return non_existing; |
| |
| default: |
| throw new Error("Bad query_kind: " + query_kind); |
| } |
| } |
| |
| |
| var TestData = []; |
| |
| [true, false].forEach((cachable) => { |
| [OBJ_MODE_FAST, OBJ_MODE_SLOW].forEach((obj_mode) => { |
| var proto_mode = cachable ? "" : "-with-slow-proto"; |
| var name = `${obj_mode}-obj${proto_mode}`; |
| var objects = []; |
| [10, 50, 100, 200, 500].forEach((prop_count) => { |
| // Create object with prop_count properties and prop_count elements. |
| obj = ObjectWithProtoKeys(5, prop_count * 2, cachable, |
| ObjectWithMixedKeys); |
| if (obj_mode == OBJ_MODE_SLOW) { |
| obj = MakeDictionaryMode(obj); |
| } |
| objects.push(obj); |
| }); |
| TestData.push({name, objects}); |
| }); |
| }); |
| |
| |
| // ============================================================================ |
| |
| function CreateTestFunction(template, object, keys) { |
| // Force a new function for each test-object to avoid side-effects due to ICs. |
| var text = "// random comment " + Math.random() + "\n" + |
| template(object, keys); |
| var func = new Function("object", "keys", text); |
| return () => func(object, keys); |
| } |
| |
| function CombineTestFunctions(tests) { |
| return () => { |
| for (var i = 0; i < tests.length; i++ ) { |
| tests[i](); |
| } |
| }; |
| } |
| |
| var TestFunctions = [ |
| { |
| name: "in", |
| // Query all keys. |
| keys: (object) => Object.keys(object), |
| template: (object, keys) => { |
| var lines = [ |
| `var result = true;`, |
| `for (var i = 0; i < keys.length; i++) {`, |
| ` var key = keys[i];`, |
| ` result = (key in object) && result;`, |
| `}`, |
| `return result;`, |
| ]; |
| return lines.join("\n"); |
| }, |
| }, |
| { |
| name: "Object.hasOwnProperty", |
| // Query only own keys. |
| keys: (object) => Object.getOwnPropertyNames(object), |
| template: (object, keys) => { |
| var lines = [ |
| `var result = true;`, |
| `for (var i = 0; i < keys.length; i++) {`, |
| ` var key = keys[i];`, |
| ` result = object.hasOwnProperty(key) && result;`, |
| `}`, |
| `return result;`, |
| ]; |
| return lines.join("\n"); |
| }, |
| }, |
| ]; |
| |
| |
| // ============================================================================ |
| // Create the benchmark suites. We create a suite for each pair of the test |
| // functions above and query kind. Each suite contains benchmarks for each |
| // object type. |
| var Benchmarks = []; |
| |
| for (var test_function_desc of TestFunctions) { |
| var test_function_name = test_function_desc.name; |
| |
| for (var query_kind of TestQueries) { |
| var benchmarks = []; |
| var suit_name = test_function_name + "--" + query_kind; |
| for (var test_data of TestData) { |
| var name = suit_name + "--" + test_data.name; |
| |
| var tests = []; |
| for (var object of test_data.objects) { |
| var keys = test_function_desc.keys(object); |
| keys = MakeKeyQueries(keys, query_kind); |
| |
| var test = CreateTestFunction(test_function_desc.template, object, |
| keys); |
| tests.push(test); |
| } |
| var run_function = CombineTestFunctions(tests); |
| var benchmark = new Benchmark(name, false, false, 0, run_function); |
| benchmarks.push(benchmark); |
| } |
| Benchmarks.push(new BenchmarkSuite(suit_name, [100], benchmarks)); |
| } |
| } |
| |
| // ============================================================================ |