Skip to content

Instantly share code, notes, and snippets.

@friendlyanon
Last active June 23, 2019 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save friendlyanon/17b680e8352a5eb6faa01c910f4c662e to your computer and use it in GitHub Desktop.
Save friendlyanon/17b680e8352a5eb6faa01c910f4c662e to your computer and use it in GitHub Desktop.
Object iteration test
Please read my first comment before drawing your own conclusion and take the points stated there into consideration as well
--------------------------------------------------------------------------------
Object iteration test in node 12.2.0, v8 7.4.288.21-node.17
Let O be object with properties x = 1, y = 1, z = 1, then iterate over the values and sum them, storing the result in variable `total`
--------------------------------------------------------------------------------
O = { x: 1, y: 1, z: 1 }
for-in x 81,606,229 ops/sec ±0.59% (94 runs sampled)
Object.keys functional x 20,670,905 ops/sec ±1.33% (90 runs sampled)
Object.keys functional with arrow x 20,912,044 ops/sec ±0.39% (91 runs sampled)
Object.keys with for of loop x 19,486,006 ops/sec ±0.67% (91 runs sampled)
Object.values functional x 31,460,789 ops/sec ±1.89% (94 runs sampled)
Object.values functional with arrow x 30,695,297 ops/sec ±2.63% (91 runs sampled)
Object.values with for of loop x 28,211,538 ops/sec ±0.65% (86 runs sampled)
Fastest is for-in
--------------------------------------------------------------------------------
class O {
constructor() {
this.x = 1;
this.y = 1;
this.z = 1;
}
}
O = new O
for-in x 70,318,394 ops/sec ±2.84% (88 runs sampled)
Object.keys functional x 20,935,056 ops/sec ±0.56% (90 runs sampled)
Object.keys functional with arrow x 21,227,321 ops/sec ±0.30% (92 runs sampled)
Object.keys with for of loop x 19,716,303 ops/sec ±0.48% (92 runs sampled)
Object.values functional x 31,754,032 ops/sec ±0.35% (94 runs sampled)
Object.values functional with arrow x 28,542,769 ops/sec ±1.79% (86 runs sampled)
Object.values with for of loop x 23,773,592 ops/sec ±0.90% (90 runs sampled)
Fastest is for-in
--------------------------------------------------------------------------------
class Proto {}
class O extends Proto {
constructor() {
super();
this.x = 1;
this.y = 1;
this.z = 1;
}
}
O = new O
for-in x 44,789,882 ops/sec ±3.01% (89 runs sampled)
Object.keys functional x 18,637,732 ops/sec ±0.65% (94 runs sampled)
Object.keys functional with arrow x 20,739,221 ops/sec ±1.91% (91 runs sampled)
Object.keys with for of loop x 19,536,007 ops/sec ±0.63% (94 runs sampled)
Object.values functional x 31,153,225 ops/sec ±0.57% (93 runs sampled)
Object.values functional with arrow x 31,444,851 ops/sec ±0.52% (94 runs sampled)
Object.values with for of loop x 28,441,749 ops/sec ±0.64% (91 runs sampled)
Fastest is for-in
--------------------------------------------------------------------------------
class Proto {}
Proto.prototype.CONSTANT = 1
class O extends Proto {
constructor() {
super();
this.x = 1;
this.y = 1;
this.z = 1;
}
}
O = new O
for-in x 1,392,724 ops/sec ±0.46% (93 runs sampled)
Object.keys functional x 20,677,449 ops/sec ±2.10% (85 runs sampled)
Object.keys functional with arrow x 20,806,197 ops/sec ±1.74% (91 runs sampled)
Object.keys with for of loop x 19,479,288 ops/sec ±1.80% (89 runs sampled)
Object.values functional x 31,982,096 ops/sec ±0.41% (91 runs sampled)
Object.values functional with arrow x 31,829,975 ops/sec ±0.40% (94 runs sampled)
Object.values with for of loop x 28,743,191 ops/sec ±0.35% (92 runs sampled)
Fastest is Object.values functional
--------------------------------------------------------------------------------
O = Object.create(null, { ... })
for-in x 3,802,896 ops/sec ±0.63% (92 runs sampled)
Object.keys functional x 4,573,414 ops/sec ±1.64% (92 runs sampled)
Object.keys functional with arrow x 4,665,355 ops/sec ±0.68% (90 runs sampled)
Object.keys with for of loop x 4,177,923 ops/sec ±0.78% (92 runs sampled)
Object.values functional x 1,217,576 ops/sec ±1.55% (94 runs sampled)
Object.values functional with arrow x 1,239,722 ops/sec ±0.37% (92 runs sampled)
Object.values with for of loop x 1,223,447 ops/sec ±0.58% (87 runs sampled)
Fastest is Object.keys functional with arrow
--------------------------------------------------------------------------------
O = Object.assign(Object.create(null), { ... })
for-in x 3,798,794 ops/sec ±0.61% (92 runs sampled)
Object.keys functional x 4,701,136 ops/sec ±0.52% (93 runs sampled)
Object.keys functional with arrow x 4,694,575 ops/sec ±0.50% (95 runs sampled)
Object.keys with for of loop x 4,337,191 ops/sec ±1.75% (90 runs sampled)
Object.values functional x 1,255,143 ops/sec ±0.55% (91 runs sampled)
Object.values functional with arrow x 1,210,711 ops/sec ±1.43% (87 runs sampled)
Object.values with for of loop x 1,160,192 ops/sec ±1.50% (89 runs sampled)
Fastest is Object.keys functional
--------------------------------------------------------------------------------
O = Object.setPrototypeOf({ ... }, null)
for-in x 23,996,197 ops/sec ±0.50% (89 runs sampled)
Object.keys functional x 19,953,472 ops/sec ±1.30% (88 runs sampled)
Object.keys functional with arrow x 20,169,071 ops/sec ±0.83% (92 runs sampled)
Object.keys with for of loop x 17,995,021 ops/sec ±3.74% (90 runs sampled)
Object.values functional x 28,807,986 ops/sec ±2.70% (92 runs sampled)
Object.values functional with arrow x 25,957,594 ops/sec ±1.97% (84 runs sampled)
Object.values with for of loop x 22,398,069 ops/sec ±2.48% (91 runs sampled)
Fastest is Object.values functional
--------------------------------------------------------------------------------
O = Object.assign(Object.setPrototypeOf({}, null), { ... })
for-in x 23,694,270 ops/sec ±0.52% (92 runs sampled)
Object.keys functional x 17,986,306 ops/sec ±0.71% (86 runs sampled)
Object.keys functional with arrow x 17,680,206 ops/sec ±1.97% (86 runs sampled)
Object.keys with for of loop x 18,036,019 ops/sec ±0.58% (92 runs sampled)
Object.values functional x 28,614,507 ops/sec ±1.37% (92 runs sampled)
Object.values functional with arrow x 28,809,917 ops/sec ±0.60% (91 runs sampled)
Object.values with for of loop x 23,461,986 ops/sec ±2.47% (81 runs sampled)
Fastest is Object.values functional with arrow
--------------------------------------------------------------------------------
O = { x: 1, y: 1, z: 1 }
O.last = 1
delete O.last
for-in x 18,376,416 ops/sec ±2.04% (91 runs sampled)
Object.keys functional x 19,848,241 ops/sec ±1.72% (93 runs sampled)
Object.keys functional with arrow x 20,022,395 ops/sec ±0.62% (92 runs sampled)
Object.keys with for of loop x 18,602,105 ops/sec ±0.57% (92 runs sampled)
Object.values functional x 29,523,600 ops/sec ±0.36% (95 runs sampled)
Object.values functional with arrow x 29,444,726 ops/sec ±0.47% (94 runs sampled)
Object.values with for of loop x 26,375,084 ops/sec ±2.22% (87 runs sampled)
Fastest is Object.values functional
--------------------------------------------------------------------------------
O = { x: 1, y: 1, z: 1 }
O.middle = 1
O.last = 0
delete O.middle
for-in x 2,908,092 ops/sec ±0.86% (93 runs sampled)
Object.keys functional x 3,693,220 ops/sec ±0.46% (93 runs sampled)
Object.keys functional with arrow x 3,643,146 ops/sec ±0.59% (90 runs sampled)
Object.keys with for of loop x 3,390,103 ops/sec ±1.36% (93 runs sampled)
Object.values functional x 995,274 ops/sec ±0.69% (94 runs sampled)
Object.values functional with arrow x 1,001,599 ops/sec ±0.53% (94 runs sampled)
Object.values with for of loop x 986,497 ops/sec ±0.47% (93 runs sampled)
Fastest is Object.keys functional
--------------------------------------------------------------------------------
O = { x: 1, y: 1, z: 1, __proto__: null }
for-in x 3,656,509 ops/sec ±0.42% (90 runs sampled)
Object.keys functional x 4,395,856 ops/sec ±2.57% (88 runs sampled)
Object.keys functional with arrow x 4,502,979 ops/sec ±0.57% (93 runs sampled)
Object.keys with for of loop x 4,402,531 ops/sec ±0.50% (93 runs sampled)
Object.values functional x 1,200,944 ops/sec ±2.19% (89 runs sampled)
Object.values functional with arrow x 1,219,022 ops/sec ±0.57% (93 runs sampled)
Object.values with for of loop x 1,212,387 ops/sec ±0.54% (91 runs sampled)
Fastest is Object.keys functional with arrow
--------------------------------------------------------------------------------
O = { x: 1, y: 1, z: 1 }
O.__proto__ = null
for-in x 24,503,901 ops/sec ±1.00% (91 runs sampled)
Object.keys functional x 20,419,240 ops/sec ±0.48% (93 runs sampled)
Object.keys functional with arrow x 19,973,444 ops/sec ±1.91% (92 runs sampled)
Object.keys with for of loop x 18,976,196 ops/sec ±0.54% (89 runs sampled)
Object.values functional x 30,001,588 ops/sec ±0.41% (95 runs sampled)
Object.values functional with arrow x 29,877,280 ops/sec ±0.45% (92 runs sampled)
Object.values with for of loop x 27,163,966 ops/sec ±1.15% (92 runs sampled)
Fastest is Object.values functional
--------------------------------------------------------------------------------
O = { x: 1, y: 1, z: 1 }
TEMP = O.__proto__
O.__proto__ = null
O.__proto__ = TEMP
O.__proto__ = null
for-in x 17,230,976 ops/sec ±0.77% (89 runs sampled)
Object.keys functional x 14,676,299 ops/sec ±1.11% (90 runs sampled)
Object.keys functional with arrow x 13,718,010 ops/sec ±0.85% (93 runs sampled)
Object.keys with for of loop x 14,357,553 ops/sec ±1.01% (89 runs sampled)
Object.values functional x 24,556,696 ops/sec ±0.57% (89 runs sampled)
Object.values functional with arrow x 24,380,558 ops/sec ±0.52% (93 runs sampled)
Object.values with for of loop x 22,225,369 ops/sec ±1.92% (93 runs sampled)
Fastest is Object.values functional
--------------------------------------------------------------------------------
O = { x: 1, y: 1, z: 1 }
TEMP = O.__proto__
O.__proto__ = null
O.__proto__ = TEMP
O.__proto__ = null
O.__proto__ = TEMP
O.__proto__ = null
for-in x 17,795,309 ops/sec ±0.81% (88 runs sampled)
Object.keys functional x 15,983,305 ops/sec ±0.40% (93 runs sampled)
Object.keys functional with arrow x 15,995,467 ops/sec ±0.38% (94 runs sampled)
Object.keys with for of loop x 14,863,531 ops/sec ±0.60% (93 runs sampled)
Object.values functional x 25,716,212 ops/sec ±0.27% (91 runs sampled)
Object.values functional with arrow x 25,516,220 ops/sec ±1.64% (96 runs sampled)
Object.values with for of loop x 23,293,134 ops/sec ±1.15% (94 runs sampled)
Fastest is Object.values functional
--------------------------------------------------------------------------------
O = { x: 1, y: 1, z: 1 }
Object.prototype has an own enumerable property added via Object.defineProperty
for-in x 1,014,930 ops/sec ±0.61% (88 runs sampled)
Object.keys functional x 21,365,935 ops/sec ±0.42% (94 runs sampled)
Object.keys functional with arrow x 21,157,673 ops/sec ±1.42% (93 runs sampled)
Object.keys with for of loop x 19,840,689 ops/sec ±0.33% (94 runs sampled)
Object.values functional x 30,176,199 ops/sec ±0.77% (91 runs sampled)
Object.values functional with arrow x 29,789,457 ops/sec ±2.17% (89 runs sampled)
Object.values with for of loop x 27,605,175 ops/sec ±0.77% (92 runs sampled)
Fastest is Object.values functional
"use strict";
const { Suite } = require("benchmark");
const printer = require("./print");
const tests = [
{
message: "O = { x: 1, y: 1, z: 1 }",
init() {
return { x: 1, y: 1, z: 1 };
},
},
{
message: `class O {
constructor() {
this.x = 1;
this.y = 1;
this.z = 1;
}
}
O = new O`,
init() {
return new class O {
constructor() {
this.x = 1;
this.y = 1;
this.z = 1;
}
};
},
},
{
message: `class Proto {}
class O extends Proto {
constructor() {
super();
this.x = 1;
this.y = 1;
this.z = 1;
}
}
O = new O`,
init() {
class Proto {}
class O extends Proto {
constructor() {
super();
this.x = 1;
this.y = 1;
this.z = 1;
}
}
return new O;
},
},
{
message: `class Proto {}
Proto.prototype.CONSTANT = 1
class O extends Proto {
constructor() {
super();
this.x = 1;
this.y = 1;
this.z = 1;
}
}`,
init() {
class Proto {}
Proto.prototype.CONSTANT = 1;
class O extends Proto {
constructor() {
super();
this.x = 1;
this.y = 1;
this.z = 1;
}
}
return new O;
},
},
{
message: "O = Object.create(null, { ... })",
init() {
return Object.create(null, {
x: {
value: 1,
configurable: true,
enumerable: true,
writable: true,
},
y: {
value: 1,
configurable: true,
enumerable: true,
writable: true,
},
z: {
value: 1,
configurable: true,
enumerable: true,
writable: true,
},
});
},
},
{
message: "O = Object.assign(Object.create(null), { ... })",
init() {
return Object.assign(Object.create(null), { x: 1, y: 1, z: 1 });
},
},
{
message: "O = Object.setPrototypeOf({ ... }, null)",
init() {
return Object.setPrototypeOf({ x: 1, y: 1, z: 1 }, null);
},
},
{
message: "O = Object.assign(Object.setPrototypeOf({}, null), { ... })",
init() {
return Object.assign(Object.setPrototypeOf({}, null), { x: 1, y: 1, z: 1 });
},
},
{
message: `O = { x: 1, y: 1, z: 1 }
O.last = 1
delete O.last`,
init() {
const O = { x: 1, y: 1, z: 1 };
O.last = 1;
delete O.last;
return O;
},
},
{
message: `O = { x: 1, y: 1, z: 1 }
O.middle = 1
O.last = 0
delete O.middle`,
init() {
const O = { x: 1, y: 1, z: 1 };
O.middle = 1;
O.last = 0;
delete O.middle;
return O;
},
},
{
message: "O = { x: 1, y: 1, z: 1, __proto__: null }",
init() {
return { x: 1, y: 1, z: 1, __proto__: null };
},
},
{
message: `O = { x: 1, y: 1, z: 1 }
O.__proto__ = null`,
init() {
const O = { x: 1, y: 1, z: 1 };
O.__proto__ = null;
return O;
},
},
{
message: `O = { x: 1, y: 1, z: 1 }
TEMP = O.__proto__
O.__proto__ = null
O.__proto__ = TEMP
O.__proto__ = null`,
init() {
const O = { x: 1, y: 1, z: 1 };
const TEMP = O.__proto__;
O.__proto__ = null;
O.__proto__ = TEMP;
O.__proto__ = null;
return O;
},
},
{
message: `O = { x: 1, y: 1, z: 1 }
TEMP = O.__proto__
O.__proto__ = null
O.__proto__ = TEMP
O.__proto__ = null
O.__proto__ = TEMP
O.__proto__ = null`,
init() {
const O = { x: 1, y: 1, z: 1 };
const TEMP = O.__proto__;
O.__proto__ = null;
O.__proto__ = TEMP;
O.__proto__ = null;
O.__proto__ = TEMP;
O.__proto__ = null;
return O;
},
},
// This test has global side effects, so it should be ran last
{
message: `O = { x: 1, y: 1, z: 1 }
Object.prototype has an own enumerable property added via Object.defineProperty`,
init() {
Object.defineProperty(Object.prototype, "__constant", {
value: 0,
configurable: true,
enumerable: true,
writable: true,
});
return { x: 1, y: 1, z: 1 };
},
},
];
function setup(suite, O) {
suite.add("for-in", function() {
let total = 0;
for (const key in O) {
total += O[key];
}
return total;
});
suite.add("Object.keys functional", function() {
return Object.keys(O).reduce(function(total, key) {
return total + O[key];
}, 0);
});
suite.add("Object.keys functional with arrow", function() {
return Object.keys(O).reduce((total, key) => total + O[key], 0);
});
suite.add("Object.keys with for of loop", function() {
let total = 0;
for (const key of Object.keys(O)) {
total += O[key];
}
return total;
});
suite.add("Object.values functional", function() {
return Object.values(O).reduce(function(total, value) {
return total + value;
}, 0);
});
suite.add("Object.values functional with arrow", function() {
return Object.values(O).reduce((total, value) => total + value, 0);
});
suite.add("Object.values with for of loop", function() {
let total = 0;
for (const value of Object.values(O)) {
total += value;
}
return total;
});
return suite;
}
async function main() {
const wait = {
suite: null,
for(suite) {
return this.suite = suite, this;
},
then(resolve) {
const { suite } = this;
this.suite = null;
suite.on("complete", function() {
printer.apply(this, arguments);
resolve();
});
suite.run();
},
};
for (let i = 0, len = tests.length;;) {
const test = tests[i];
console.log("%s\n", test.message);
await wait.for(setup(new Suite, test.init()));
if (++i >= len) break;
console.log(`
--------------------------------------------------------------------------------
`);
}
}
console.log(`--------------------------------------------------------------------------------
Object iteration test in node %s, v8 %s
Let O be object with properties x = 1, y = 1, z = 1, then iterate over the values and sum them, storing the result in variable \`total\`
--------------------------------------------------------------------------------
`, process.versions.node, process.versions.v8);
main().then(null, console.error);
@friendlyanon
Copy link
Author

friendlyanon commented Jul 4, 2018

The results are really interesting considering the latest meaningful report about this from https://github.com/davidmarkclements/v8-perf#iterating-over-objects
The test results are done using the files in that repo, with the slight modifications of having obj moved into the module scope for easier setup between tests and the for loops were changed to for of.
Something more to read on the topic from the v8 team: https://v8project.blogspot.com/2017/03/fast-for-in-in-v8.html

What I find most interesting is that the community endorsed way of creating a null proto object (Object.create(null)) is actually the worst performing out of all the options, whereas the ways of making null proto objects that are not recommended, not used much and have all kinds of warnings attached on community resources (__proto__ and Object.setPrototypeOf({}, null)) perform much better or are not hit by performance penalties at all.

Conclusion: using Object.{keys,values,entries} is preferable for the average user coupled with for of loop for safety, to avoid accidental function creation spam and to keep the code modern, whereas for someone who knows exactly what and how is happening in their code, is experienced enough and needs more speed then for in + null proto objects are the way to go.

It seems that between node 10 and 12 there was a huge regression that deoptimized for-in key iteration for null proto objects. That is unfortunate.
However, Object.create() is definitely to be avoided, because it has ugly performance consequences when it comes to iteration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment