Home | History | Annotate | Download | only in PropertyQueries
      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 // ============================================================================
    275