-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
improve Dictionary
performance
#5202
Conversation
@kzc this is #5172 (comment) alongside some clean-up. |
This will have brutal timings because it creates a temporary Array of keys: size: function() {
return Object.keys(this.values).length;
}, Can you compare timings against a for/in counting loop that creates no temporaries?
Maybe it doesn't matter if that's the case. |
That was my reason for not paying attention, but your suggestion obviously sounds better 👍 |
If this is the only use of
then remove it and replace it with
which need only iterate a single key. |
|
My timings hunch for
The V8 team must have optimized that path. |
This poor timing for obj_empty relative to obj_size is unexpected for node v16:
|
If you see similar timings on your machine with the latest version of NodeJS, then just keep the |
TIL |
Made me look:
Does it matter for anything in uglify-js? Perhaps to look up the property |
On its own it seems harmless, but turns out it is symptomatic of a larger issue: $ node -v
v0.10.48
$ echo 'var o=Object.create(null); o.__proto__ = 42; for (var k in o) console.log(k);' | node
$ node -v
v16.13.0
$ echo 'var o=Object.create(null); o.__proto__ = 42; for (var k in o) console.log(k);' | node
__proto__
so we can't use |
You could switch to using the legacy Dictionary implementation if that |
Agreed − just pondering if I could minimise code duplication... 🤔 |
By the way, in testing my original patch against old NodeJS versions I was seeing failures which I initially mistakenly attributed to my patch - but then I tried again without the patch and the failures remained. It appears that you are running the latest minor+patch versions of each NodeJS release in CI testing, whereas my local versions of NodeJS are not the latest of each major release. So some V8 bug fixes are not present in my node versions, and many tests fails as result for node versions 12.x and earlier on my local machine. It's not worth addressing but the solution would be to just raise the |
If you don't mind giving me the specific Node versions, I'm gonna catch'em all 👻 |
... actually, if those test failures you've encountered are due to language "features", I can just try every vXXX.0.0 and that should cover all the bases, right? Of course if it is some bug you've run into in some intermediate verions, then this methodology won't work... |
Again, I don't think it's worth the trouble, but here are my local versions of NodeJS:
I think v14 and v16 were fine. Every time a new V8 feature was introduced it took a major version to stabilize. |
True. No way to know without testing every point release. Don't bother. |
I shudder to think how many of these got blamed on |
Just tested with and without |
var values = function() {
var values = new Array(100000);
for (var i = values.length; --i >= 0;) {
values[i] = Math.floor(Math.random() * values.length);
}
return values;
}();
function test(fn) {
for (var i = 10; --i >= 0;) {
fn();
}
var start = Date.now();
for (var i = 100; --i >= 0;) {
fn();
}
console.log(fn.name + "\t" + (1e-3 * (Date.now() - start)).toFixed(3) + "ms");
}
var obj = Object.create(null);
function obj_insert() {
for (var i = 0; i < values.length; i++) {
obj[i] = values[i];
}
}
function obj_keys() {
return Object.keys(obj).length;
}
function obj_loop() {
var count = 0;
for (var k in obj) {
count++;
}
return count;
}
function obj_empty() {
for (var k in obj) {
return false;
}
return true;
}
test(obj_insert);
test(obj_keys);
test(obj_loop);
test(obj_empty); Seems to be consistent across versions of v8, except for $ node -v
v0.10.48
$ cat test.js | node
obj_insert 0.047ms
obj_keys 0.784ms
obj_loop 1.497ms
obj_empty 0.165ms $ node -v
v4.9.1
$ cat test.js | node
obj_insert 0.054ms
obj_keys 0.843ms
obj_loop 2.274ms
obj_empty 0.048ms $ node -v
v8.17.0
$ cat test.js | node
obj_insert 0.105ms
obj_keys 1.056ms
obj_loop 1.175ms
obj_empty 1.076ms $ node -v
v12.22.7
$ cat test.js | node
obj_insert 0.117ms
obj_keys 0.772ms
obj_loop 0.925ms
obj_empty 0.769ms $ node -v
v16.13.0
$ cat test.js | node
obj_insert 0.106ms
obj_keys 0.705ms
obj_loop 0.846ms
obj_empty 0.753ms |
If I had to guess, it appears when a for/in loop is used in any capacity it will preemptively call |
Another $ echo 'var {__proto__:a} = {__proto__:42}; console.log(a);' | node
[Object: null prototype] {} $ echo 'var {__proto__:a} = {"__proto__":42}; console.log(a);' | node
[Object: null prototype] {} $ echo 'var {__proto__:a} = {["__proto__"]:42}; console.log(a);' | node
42 $ echo 'var {"__proto__":a} = {__proto__:42}; console.log(a);' | node
[Object: null prototype] {} $ echo 'var {"__proto__":a} = {"__proto__":42}; console.log(a);' | node
[Object: null prototype] {} $ echo 'var {"__proto__":a} = {["__proto__"]:42}; console.log(a);' | node
42 $ echo 'var {["__proto__"]:a} = {__proto__:42}; console.log(a);' | node
[Object: null prototype] {} $ echo 'var {["__proto__"]:a} = {"__proto__":42}; console.log(a);' | node
[Object: null prototype] {} $ echo 'var {["__proto__"]:a} = {["__proto__"]:42}; console.log(a);' | node
42 |
I think it's by design to distinguish between the property and the prototype reference. But yeah, the rules governing destructuring are pretty weird. |
True to form, Node.js v6.0.0 for instance spew out 💎 like these: Running test [mangle_arrow_1]
!!! Invalid input or expected stdout
---INPUT---
{
var N = 1;
((o, {
pname: p
} = o, {
[p + N]: v
} = o) => {
let N;
console.log(v);
})({
pname: "x",
x1: "PASS"
});
}
---EXPECTED STDOUT---
PASS
---ACTUAL ERROR---
ReferenceError: p is not defined
Running test [mangle_arrow_1_toplevel]
!!! Invalid input or expected stdout
---INPUT---
{
var N = 1;
((o, {
pname: p
} = o, {
[p + N]: v
} = o) => {
let N;
console.log(v);
})({
pname: "x",
x1: "PASS"
});
}
---EXPECTED STDOUT---
PASS
---ACTUAL ERROR---
ReferenceError: p is not defined
Running test [mangle_arrow_2]
!!! Invalid input or expected stdout
---INPUT---
{
var N = 1;
(({
pname: p = "x",
i: n = N
}, {
[p + n]: v
}) => {
let N;
console.log(v);
})({}, {
x1: "PASS"
});
}
---EXPECTED STDOUT---
PASS
---ACTUAL ERROR---
ReferenceError: p is not defined
Running test [mangle_arrow_2_toplevel]
!!! Invalid input or expected stdout
---INPUT---
{
var N = 1;
(({
pname: p = "x",
i: n = N
}, {
[p + n]: v
}) => {
let N;
console.log(v);
})({}, {
x1: "PASS"
});
}
---EXPECTED STDOUT---
PASS
---ACTUAL ERROR---
ReferenceError: p is not defined |
Increasing random failures on node-v6 recently? Bound to be all sorts of bugs when they're chasing JIT performance. |
In those particular cases I think it's more to do with v6 being the first version with destructuring, so they are just stumbling along the way. I don't think they cared too much about performance until v10 − at least if they did it wasn't showing up 🤣 |
Here comes Node.js v8.0.0: (async function f() {
return 42 in f();
})();
console.log("PASS"); $ cat issue-4974.js | node
node: src\async-wrap.cc:621: Assertion `(env()->current_async_id()) == (0)' failed. |
It makes sense that the first version to have a particular feature to be unstable with that feature. You might consider bumping the node_version of the tests that tend to fail for a given node version. |
No description provided.