Index: test/js-perf-test/PropertyQueries/property-queries.js |
diff --git a/test/js-perf-test/PropertyQueries/property-queries.js b/test/js-perf-test/PropertyQueries/property-queries.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8c5498bd6569a55b8bec84c7beca8173ba03fe43 |
--- /dev/null |
+++ b/test/js-perf-test/PropertyQueries/property-queries.js |
@@ -0,0 +1,260 @@ |
+// 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 o = {}; |
+ for (var i = 0; i < count; i++) { |
+ var key = keyGen(i + keyOffset); |
+ o[key] = "value"; |
+ } |
+ return o; |
+} |
+ |
+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; |
+ delete obj.foo; |
+ obj[1e9] = 0; |
+ return obj; |
+} |
+ |
+function Internalize(s) { |
+ return Object.keys({[s]:0})[0]; |
+} |
+ |
+function Deinternalize(s) { |
+ return [...s].join(""); |
+} |
+ |
+// ============================================================================ |
+ |
+const QUERY_INTERNALIZED_PROP = "query-internalized-prop"; |
+const QUERY_DEINTERNALIZED_PROP = "query-deinternalized-prop"; |
+const QUERY_NON_EXISTING_INTERNALIZED_PROP = |
+ "query-non-existing-internalized-prop"; |
+const QUERY_NON_EXISTING_DEINTERNALIZED_PROP = |
+ "query-non-existing-deinternalized-prop"; |
+const QUERY_ELEMENT = "query-element"; |
+const QUERY_ELEMENT_AS_STRING = "query-element-as-string"; |
+const QUERY_NON_EXISTING_ELEMENT = "query-non-existing-element"; |
+ |
+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, 1000].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); |
+} |
+ |
+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) { |
+ for (var test_data of TestData) { |
+ var benchmarks = []; |
+ var name = test_function_name + "--" + query_kind + "--" + test_data.name; |
+ |
+ for (var object of test_data.objects) { |
+ var keys = test_function_desc.keys(object); |
+ keys = MakeKeyQueries(keys, query_kind); |
+ |
+ var test_function = CreateTestFunction(test_function_desc.template, |
+ object, keys); |
+ |
+ var benchmark = new Benchmark(name, false, true, 400, test_function); |
+ benchmarks.push(benchmark); |
+ } |
+ Benchmarks.push(new BenchmarkSuite(name, [100], benchmarks)); |
+ } |
+ } |
+} |
+ |
+// ============================================================================ |