// Copyright 2022 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 0 prototypes each having #keys properties // generated by given keyGen. function ObjectWithOwnKeys(depth, keys, keyGen = ObjectWithProperties) { var o = keyGen(keys); var current = o; var keyOffset = 0; for (var i = 0; i < 1; i++) { keyOffset += keys; current.__proto__ = keyGen(keys, keyOffset); } 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_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_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_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 = []; [OBJ_MODE_FAST, OBJ_MODE_SLOW].forEach((obj_mode) => { var name = `${obj_mode}-obj`; var objects = []; [10, 50, 100, 200, 500].forEach((prop_count) => { // Create object with prop_count properties and prop_count elements. obj = ObjectWithOwnKeys(5, prop_count * 2, ObjectWithMixedKeys); if (obj_mode == OBJ_MODE_SLOW) { obj = MakeDictionaryMode(obj); } objects.push(obj); }); TestData.push({name, objects}); }); // ============================================================================ function CreateTestFunction(testMethod, object, keys) { // Force a new function for each test-object to avoid side-effects due to ICs. var text = ` // random comment ${Math.random()} let results = []; for (var key in keys) { results.push(${testMethod}(object, key)); } return results;`; var func = new Function("object", "keys", text); return () => func(object, keys); } function CombineTestFunctions(tests) { return () => { for (var i = 0; i < tests.length; i++ ) { let results = tests[i](); results.push(1); globalThis.results = results; } }; } var TestMethods = { "ObjectGetOwnPropertyDescriptor": "Object.getOwnPropertyDescriptor", "ReflectGetOwnPropertyDescriptor": "Reflect.getOwnPropertyDescriptor", }; var TestTargets = { "object": { target(object) { return object; } }, "proxy-no-trap": { target(object) { return new Proxy(object, {}); }, }, "proxy-reflect": { target(object) { return new Proxy(object, { getOwnPropertyDescriptor(target, propertyKey) { return Reflect.getOwnPropertyDescriptor(target, propertyKey); } }); } }, "proxy-proxy-no-trap": { target(object) { let proxy = this["proxy-no-trap"].target(object); return new Proxy(proxy, {}); } }, "proxy-proxy-reflect": { target(object) { let proxy = this["proxy-no-trap"].target(object); return new Proxy(object, { getOwnPropertyDescriptor(target, propertyKey) { return Reflect.getOwnPropertyDescriptor(target, propertyKey); } }); } } }; // ============================================================================ // 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_method_name, test_method_entry] of Object.entries(TestMethods)) { for (var [test_target_name, test_target] of Object.entries(TestTargets)) { for (var query_kind of TestQueries) { var benchmarks = []; var suit_name = `${test_method_name}--${test_target_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 = Object.getOwnPropertyNames(object); keys = MakeKeyQueries(keys, query_kind); var test = CreateTestFunction(test_method_entry, 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)); } } } // ============================================================================