// 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)); } } // ============================================================================