Uh oh!
There was an error while loading. Please reload this page.
events: remove emit micro-optimizations - #16869
Conversation
With improvements in V8, using separate emit functions is no longer necessary and can instead be replaced by the spread operator. improvement confidence p.value events/ee-emit.js n=2000000 2.98 % 0.09852489 events/ee-emit-2-args.js n=2000000 4.19 % *** 0.0001914216 events/ee-emit-6-args.js n=2000000 61.69 % *** 6.611964e-35 events/ee-emit-diff-args.js n=2000000 -0.36 % 0.305069 events/ee-once.js n=20000000 6.42 % *** 1.27831e-06
apapirovski
commented
Nov 7, 2017
Benchmark CI: https://ci.nodejs.org/job/benchmark-node-micro-benchmarks/23/ Results |
mscdex
commented
Nov 7, 2017
We should just have one ee-emit.js benchmark which has a parameter for number of args, something like 0-6 maybe. |
Also, we should probably make the number of listeners configurable as well, so that we can make sure there are no regressions for smaller numbers of listeners (e.g. 2-9). |
apapirovski
commented
Nov 7, 2017
Is the AIX failure related? Anyone know? Doesn't seem like it but... |
lpinca
commented
Nov 7, 2017
It is failing since yesterday, not related. |
| const ee = new EventEmitter(); | ||
| for (var k = 0; k < 10; k += 1) |
There was a problem hiding this comment.
Coding style question: why not let? (Not just here but throughout the patch)
There was a problem hiding this comment.
its much slower because it creates a closure for the variable in each loop iteration
There was a problem hiding this comment.
@mathiasbynens We have a lint rule re: this so that's the main reason. I think @TimothyGu tried to change it recently and feedback from @bmeurer was that we shouldn't quite yet.
There was a problem hiding this comment.
Here's the relevant PR with more conversation: #15648
| handler.apply(this, args); | ||
| } else { | ||
| const len = handler.length; | ||
| const listeners = arrayClone(handler, len); |
There was a problem hiding this comment.
Can you leave a TODO here to consider switching to Array.prototype.slice once V8 6.4 lands in Node? Or even to evaluate the idea of avoiding the defensive copy on emit and rather making sure that the handler itself is never mutated?
bmeurer
left a comment
There was a problem hiding this comment.
LGTM modulo comment.
Seeing this happening makes me happy!
There was a problem hiding this comment.
Making my concerns more explicit ...
We should just have one ee-emit.js benchmark which has a parameter for number of args, something like 0-6 maybe.
Also, we should probably make the number of listeners configurable as well, so that we can make sure there are no regressions for smaller numbers of listeners (e.g. 2-9).
New benchmark CI for the changes: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/28/ I have absolutely no clue why there's exactly one benchmark that tanked on the new version: As far as I can tell this is almost equivalent to the old |
apapirovski
commented
Nov 8, 2017
Ok, that benchmark needs to be tweaked. There's an optimization happening when using |
Ok, created a second version of the benchmark that doesn't use Benchmark CI: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/31/ (The reason I changed it is that using |
210602d to
7ddf389Compareapapirovski
commented
Nov 8, 2017
New benchmark results: |
apapirovski
commented
Nov 11, 2017
@mscdex PTAL |
@mscdex or anyone else that reviewed this, PTAL. The "changes requested" needs to be dismissed before we can land this. |
We can make things faster for multiple listeners by adding some fast paths in functionarrayClone(arr,n){switch(n){case0: return[];case1: return[arr[0]];case2: return[arr[0],arr[1]];case3: return[arr[0],arr[1],arr[2]];case4: return[arr[0],arr[1],arr[2],arr[3]];case5: return[arr[0],arr[1],arr[2],arr[3],arr[4]];}// Not included in 'default' case because of perf issue with `const` in// a switch caseconstcopy=newArray(n);copy[0]=arr[0];copy[1]=arr[1];copy[2]=arr[2];copy[3]=arr[3];copy[4]=arr[4];copy[5]=arr[5];for(vari=6;i<n;++i)copy[i]=arr[i];returncopy;}I chose 5 as the max array length for fast paths as that seems like a reasonable limit to me. Also, 0 and 1-length arrays are supported for backwards compatibility, otherwise we could remove those cases since those kinds of arrays will not be generated internally (I'm thinking about modules that may directly mutate arrays in |
@mscdex Isn't that a bit outside the scope of this PR specifically, considering this PR does not touch that piece of code? |
@TimothyGu No? To me this PR is about improving |
@mscdex I guess @apapirovski can say for sure. Personally, I thought it was about making the code more maintainable/understandable by removing CrankshaftScript optimizations that no longer provide significant benefit. The fact that it also improves (rather than merely preserves) performance may have been incidental. I also prefer PRs to remain narrowly scoped on principle. If we put the |
From what I've seen, benchmarks are often not checked when backporting, which is especially important when going from TurboFan to Crankshaft. Whatever though, I just thought I'd throw the perf improvement out there for anyone interested. |
jasnell
commented
Nov 14, 2017
I see no reason not to add @mscdex's suggestion as a second separate commit in this PR. |
bmeurer
commented
Nov 14, 2017
@mscdex These |
mscdex
commented
Nov 14, 2017
@bmeurer I'm not sure what you're asking, but we've always made a copy up front in case the event handlers change during the execution of event handlers. |
bmeurer
commented
Nov 14, 2017
Right, but wouldn't it be possible to treat the handlers array as copy on write? Such that when iterating over them, you sort of take ownership of the handlers array and in case someone adds/removes handlers, you create a new Array instead of mutating the existing one. |
TimothyGu
commented
Nov 14, 2017
@bmeurer How would do you get notified when the array changes? |
Trott
commented
Nov 14, 2017
I could be wrong, but it sure looks like we may be spiraling off into a conversation about whether and how to implement the |
Trott
commented
Nov 14, 2017
By the way, the only thing preventing this from landing at this point is the objection from @mscdex. So I guess the question is whether the objection has been effectively cleared by the benchmark fixes/changes that @apapirovski did in response? Or is there still an objection to landing this without the |
Sorry, I haven't had time the past couple of days to address much of what's been discussed here. But as mentioned, the intent was mostly to remove "ugly" code that wasn't improving performance any longer. The speed up for emits with many arguments is a just a nice boon. (Further evidenced by the quote in the original: "With improvements in V8, using separate emit functions is no longer necessary. ") Re: the ensuing discussion, I think @bmeurer has a valid point re: allocating new handler array when an event is attached as opposed to emitted. That's something that could be benchmarked and tested. I might look at it if I have time this week. In the meantime, I'll be landing this shortly. |
apapirovski
commented
Nov 14, 2017
Landed in f44f18a |
With improvements in V8, using separate emit functions is no longer necessary and can instead be replaced by the spread operator. improvement confidence p.value events/ee-emit.js n=2000000 2.98 % 0.09852489 events/ee-emit-2-args.js n=2000000 4.19 % *** 0.0001914216 events/ee-emit-6-args.js n=2000000 61.69 % *** 6.611964e-35 events/ee-emit-diff-args.js n=2000000 -0.36 % 0.305069 events/ee-once.js n=20000000 6.42 % *** 1.27831e-06 PR-URL: #16869 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Evan Lucas <evanlucas@me.com> Reviewed-By: Bryan English <bryan@bryanenglish.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Benedikt Meurer <benedikt.meurer@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Brian White <mscdex@mscdex.net>
With improvements in V8, using separate emit functions is no longer necessary and can instead be replaced by the spread operator. improvement confidence p.value events/ee-emit.js n=2000000 2.98 % 0.09852489 events/ee-emit-2-args.js n=2000000 4.19 % *** 0.0001914216 events/ee-emit-6-args.js n=2000000 61.69 % *** 6.611964e-35 events/ee-emit-diff-args.js n=2000000 -0.36 % 0.305069 events/ee-once.js n=20000000 6.42 % *** 1.27831e-06 PR-URL: #16869 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Evan Lucas <evanlucas@me.com> Reviewed-By: Bryan English <bryan@bryanenglish.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Benedikt Meurer <benedikt.meurer@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Brian White <mscdex@mscdex.net>

With improvements in V8, using separate emit functions is no longer necessary. This is not applicable to v8.x or earlier (the earlier version that's baking is though).
ee-emit-diff-args.jswas a custom benchmark to confirm whether there was any deoptimization happening when the function pattern — number of arguments passed in — would change unpredictably (between 0 - 3 arguments, so what used to be the fast path). I also had a version of that benchmark with 0-4 arguments and that one was +20% or so, due to the improvements inemitperformance for more than 3 arguments.Checklist
make -j4 test(UNIX), orvcbuild test(Windows) passesAffected core subsystem(s)
benchmark, events