-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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
events: several perf related adjustments #16212
events: several perf related adjustments #16212
Conversation
Replace truthy/falsey checks of _events and _events[type] with comparisons to undefined for better performance: events/ee-add-remove.js n=250000 5.30 % *** 4.260028e-07 events/ee-emit.js n=2000000 4.18 % *** 1.026649e-05 This has a knock-on effect on modules that use lots of events, e.g.: http2/headers.js nheaders=0 n=1000 2.60 % *** 0.000298338
Previously, console had to be compiled in case it was not available but this is no longer necessary - remove it. Refs: nodejs#15111
lib/events.js
Outdated
|
||
if (events === undefined) | ||
ret = []; | ||
if (!events) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this was changed back to !events
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Complete accident. Will push a fix shortly.
lib/events.js
Outdated
if (evlistener === undefined) | ||
ret = []; | ||
const evlistener = events[type]; | ||
if (!evlistener) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, total accident. Fixed now. Thanks for flagging!
f1b2966
to
d16c788
Compare
I started a benchmark CI job, the 19 one (still in the queue at the moment): https://ci.nodejs.org/job/benchmark-node-micro-benchmarks/ |
if (events) | ||
doError = (doError && events.error == null); | ||
const events = this._events; | ||
if (events !== undefined) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes like this really warrant a CITGM run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I completely agree. 👍 I tried to flag it in my description of the PR but it's a bit of a wall of text.
Did you benchmark this against master or a node canary branch? I just checked using spread in master the other week for |
@mscdex Master. I mean it's slower if we completely switch to using spread (it loses out big time for the 0-3 cases) but this is sort of a hybrid approach. (When I say completely, I mean getting rid of the separate emit functions and inlining it with spread.) |
Benchmark CI results:
Keep in mind we don't have a bench for passing emit more than 2 args. The ee-emit-multi-args is just 2 arguments and matches my results above. |
Have you tested this on current node v8.x as well? If v8.x does not get V8 6.2 then we want to make sure we don't include a performance regression in that branch. |
@mscdex Can test this shortly. Good point. |
Just a quick test on v8.x to make sure there aren't any regressions. That said, I think the most sensible approach would be to have a separate backport PR & run the benchmark CI there. The first commit in here needs to be backported anyway as it doesn't land cleanly. |
lib/events.js
Outdated
else if (typeof evlistener === 'function') | ||
ret = [evlistener.listener || evlistener]; | ||
return [evlistener.listener || evlistener]; | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The else conditions become obsolete here due to the return
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Speaking of, I think the whole thing can be adjusted to not be an 'else' since the first if
returns. I'll force push an adjusted commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is what I implied 😄
Each conditional branch in EventEmitter.prototype.listeners assigns its return value to a variable ret which is returned at the end. Instead just return from within each branch. This is both clearer and more performant. events/ee-listeners.js n=5000000 3.65 % *** 3.359171e-10
With recent changes in V8, it is now as performant or faster to use spread parameter within EventEmitter.prototype.emit, especially in cases where looping over arguments is required. events/ee-emit.js n=2000000 4.40 % *** 1.505543e-06 events/ee-emit-1-arg.js n=2000000 2.16 % *** 2.434584e-10 events/ee-emit-2-args.js n=2000000 1.05 % ** 0.001764852 events/ee-emit-3-args.js n=2000000 2.18 % *** 3.234954e-08 events/ee-emit-6-args.js n=2000000 17.17 % *** 1.298702e-103 events/ee-emit-10-args.js n=2000000 17.14 % *** 1.144958e-97 This has a knock-on effect for modules that use events extensively, such as http2: http2/headers.js nheaders=0 n=1000 2.10 % *** 6.792106e-11
Due to changes in V8 in 6.0 & 6.1, it's no longer necessary to copy arguments to avoid deopt. Just call .apply with arguments. Retains fast cases for 0-3 arguments. events/ee-once-4-args.js n=20000000 11.58 % *** 1.310379e-05
d16c788
to
0585047
Compare
Replaced all |
Could somebody please start a CITGM for this? Want to make sure the whole world doesn't burn because of the first commit that changes |
CITGM: https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/1012/ @apapirovski you should be able to it yourself. |
@lpinca not quite there yet, not until Wednesday :) Thanks for starting it! |
No clue how to interpret the results. No obvious red flags but some of these failures seem to be a regular occurrence and with others I can't tell. |
Yes, honestly I also don't know how to interpret CITGM results. Not sure what are the expected failures nowadays. |
Due to changes in V8 in 6.0 & 6.1, it's no longer necessary to copy arguments to avoid deopt. Just call .apply with arguments. Retains fast cases for 0-3 arguments. events/ee-once-4-args.js n=20000000 11.58 % *** 1.310379e-05 PR-URL: nodejs#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
@jasnell I would love to see this land in 9.0.0 if at all possible so it can begin baking immediately. Thanks! |
This does not land cleanly in 8.x It would need to be manually backported. As it seems this perf improvement needs some time to bake we may want to wait for it to live in 9.x for a bit before backporting |
@MylesBorins this needs to be benchmarked again when landed on 8. It likely needs V8 6.2. |
@mcarter @MylesBorins This has been tested against v8.x already as @mscdex requested it. Most of the perf improvements were nearly identical. |
Actually I don't think it was tested on V8 6.2. Master still was on V8 6.1 when you published the benchmark numbers |
@targos I think we had escape analysis enabled on master at that point |
Replace truthy/falsey checks of _events and _events[type] with comparisons to undefined for better performance: events/ee-add-remove.js n=250000 5.30 % *** 4.260028e-07 events/ee-emit.js n=2000000 4.18 % *** 1.026649e-05 This has a knock-on effect on modules that use lots of events, e.g.: http2/headers.js nheaders=0 n=1000 2.60 % *** 0.000298338 PR-URL: nodejs/node#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Previously, console had to be compiled in case it was not available but this is no longer necessary - remove it. PR-URL: nodejs/node#16212 Refs: nodejs/node#15111 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Each conditional branch in EventEmitter.prototype.listeners assigns its return value to a variable ret which is returned at the end. Instead just return from within each branch. This is both clearer and more performant. events/ee-listeners.js n=5000000 3.65 % *** 3.359171e-10 PR-URL: nodejs/node#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
With recent changes in V8, it is now as performant or faster to use spread parameter within EventEmitter.prototype.emit, especially in cases where looping over arguments is required. events/ee-emit.js n=2000000 4.40 % *** 1.505543e-06 events/ee-emit-1-arg.js n=2000000 2.16 % *** 2.434584e-10 events/ee-emit-2-args.js n=2000000 1.05 % ** 0.001764852 events/ee-emit-3-args.js n=2000000 2.18 % *** 3.234954e-08 events/ee-emit-6-args.js n=2000000 17.17 % *** 1.298702e-103 events/ee-emit-10-args.js n=2000000 17.14 % *** 1.144958e-97 This has a knock-on effect for modules that use events extensively, such as http2: http2/headers.js nheaders=0 n=1000 2.10 % *** 6.792106e-11 PR-URL: nodejs/node#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Due to changes in V8 in 6.0 & 6.1, it's no longer necessary to copy arguments to avoid deopt. Just call .apply with arguments. Retains fast cases for 0-3 arguments. events/ee-once-4-args.js n=20000000 11.58 % *** 1.310379e-05 PR-URL: nodejs/node#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Replace truthy/falsey checks of _events and _events[type] with comparisons to undefined for better performance: events/ee-add-remove.js n=250000 5.30 % *** 4.260028e-07 events/ee-emit.js n=2000000 4.18 % *** 1.026649e-05 This has a knock-on effect on modules that use lots of events, e.g.: http2/headers.js nheaders=0 n=1000 2.60 % *** 0.000298338 PR-URL: nodejs/node#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Previously, console had to be compiled in case it was not available but this is no longer necessary - remove it. PR-URL: nodejs/node#16212 Refs: nodejs/node#15111 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Each conditional branch in EventEmitter.prototype.listeners assigns its return value to a variable ret which is returned at the end. Instead just return from within each branch. This is both clearer and more performant. events/ee-listeners.js n=5000000 3.65 % *** 3.359171e-10 PR-URL: nodejs/node#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
With recent changes in V8, it is now as performant or faster to use spread parameter within EventEmitter.prototype.emit, especially in cases where looping over arguments is required. events/ee-emit.js n=2000000 4.40 % *** 1.505543e-06 events/ee-emit-1-arg.js n=2000000 2.16 % *** 2.434584e-10 events/ee-emit-2-args.js n=2000000 1.05 % ** 0.001764852 events/ee-emit-3-args.js n=2000000 2.18 % *** 3.234954e-08 events/ee-emit-6-args.js n=2000000 17.17 % *** 1.298702e-103 events/ee-emit-10-args.js n=2000000 17.14 % *** 1.144958e-97 This has a knock-on effect for modules that use events extensively, such as http2: http2/headers.js nheaders=0 n=1000 2.10 % *** 6.792106e-11 PR-URL: nodejs/node#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Due to changes in V8 in 6.0 & 6.1, it's no longer necessary to copy arguments to avoid deopt. Just call .apply with arguments. Retains fast cases for 0-3 arguments. events/ee-once-4-args.js n=20000000 11.58 % *** 1.310379e-05 PR-URL: nodejs/node#16212 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Quick ping to see if we want to include this in v8.x |
At this point I don't see a backport to 8.x as being critical |
This PR covers a few different perf related changes to
EventEmitter
. Everything is separated into stand-alone commits. Might be easier to review as such but overall there actually there aren't that many lines being changed so it should be pretty easy to get through.All performance benchmarks cited below are based on 100 or 200 sets (as necessary to get the right certainty) and are independent of other changes within the PR (so something like the onceWrapper result is strictly for that change and does not include the changes that precede it).
Feedback & reviews greatly appreciated!
events: stricter prop & variable checks for perf
Replace truthy/falsey checks of
EventEmitter.prototype._events
andEventEmitter.prototype._events[type]
with comparisons toundefined
for better performance:This has a knock-on effect on modules that use lots of events, e.g.:
It's possible this could affect user-land code if users are interacting with
_events
directly. We should strongly consider running CITGM. Maybe this is semver-major? Not sure since it's not documented and leading _ obviously signifies a private prop.events: remove unnecessary console instantiation
Previously, console had to be compiled in case it was not available but this is no longer necessary - remove it.
Refs: #15111
events: return values directly in listeners
Each conditional branch in
EventEmitter.prototype.listeners
assigns its return value to a variableret
which is returned at the end. Instead just return from within each branch. This is both clearer and more performant.events: use spread function param in emit
With recent changes in V8, it is now as performant or faster to use spread parameter within
EventEmitter.prototype.emit
, especially in cases where looping overarguments
is required.This has a knock-on effect for modules that use events extensively, such as http2:
events: onceWrapper apply directly with arguments
Due to changes in V8, it's no longer necessary to copy arguments to avoid deopt. Just call
apply
witharguments
. Retains fast cases for 0-3 arguments.Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
events