Skip to content
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: improve performance for emit(), on(), and listeners() #13155

Closed
wants to merge 3 commits into from

Conversation

mscdex
Copy link
Contributor

@mscdex mscdex commented May 22, 2017

Results:

                                                improvement confidence      p.value
 events/ee-add-remove.js listeners=2 n=9000000      7.19 %        *** 1.372451e-06
 events/ee-emit.js listeners=2 n=100000000         23.17 %        *** 4.828907e-66
 events/ee-emit.js listeners=3 n=100000000         26.26 %        *** 1.538432e-24
 events/ee-emit.js listeners=4 n=100000000         52.78 %        *** 3.552876e-25
 events/ee-emit.js listeners=5 n=100000000         54.99 %        *** 9.689188e-105
 events/ee-emit.js listeners=10 n=100000000        18.32 %        *** 5.38142e-30
 events/ee-listeners.js listeners=2 n=55000000     13.62 %        *** 4.538509e-64
 events/ee-listeners.js listeners=3 n=55000000     12.38 %        *** 8.824486e-67
 events/ee-listeners.js listeners=4 n=55000000     10.98 %        *** 1.828026e-65
 events/ee-listeners.js listeners=10 n=55000000     1.31 %        *** 2.647896e-07

CI: https://ci.nodejs.org/job/node-test-pull-request/8230/

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)
  • events

@mscdex mscdex added events Issues and PRs related to the events subsystem / EventEmitter. performance Issues and PRs related to the performance of Node.js. labels May 22, 2017
@nodejs-github-bot nodejs-github-bot added the events Issues and PRs related to the events subsystem / EventEmitter. label May 22, 2017
@jasnell
Copy link
Member

jasnell commented May 23, 2017

I totally get the why of this change, especially given the performance regressions we've seen in events lately that will hopefully be fixed once the v8 patch lands... but, I can't say I'm too thrilled with this change because of the increased complexity of the function calls.

@mscdex
Copy link
Contributor Author

mscdex commented May 23, 2017

@jasnell If you're referring to the emit() changes, both the manual inlining and the partial loop unrolling provide a measurable improvement in performance. Either change can be reverted and there'll still be some improvement, but it won't be as much as both of them together.

As far as reducing code, there's not much that can be done without hindering performance, except compile-time macros, but those have their own problems.

@@ -8,6 +8,7 @@ function main(conf) {
var n = conf.n | 0;

var ee = new EventEmitter();
ee.setMaxListeners(listeners + 1);
Copy link
Member

@lpinca lpinca May 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is listeners defined?

Edit: nvm, I only watched first commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@lpinca
Copy link
Member

lpinca commented May 23, 2017

I'm not a fan of code duplication but the performance improvements justify this.

@fhinkel
Copy link
Member

fhinkel commented May 23, 2017

Can you try this with Node with V8 60? I wonder if we've fixed the performance glitches and the loop unrolling. /cc @nodejs/v8
Edit: Use this

@simonkcleung
Copy link

No arguments pass to emit() in the test?

@MylesBorins
Copy link
Contributor

Can you try this with V8 60 (Ubuntu executable)

+1. Since 8.x is planning to ship with TF + I we should likely only land this if there is a persistent perf improvement seen on the new toolchain

@bmeurer
Copy link
Member

bmeurer commented May 23, 2017

Did you try with TurboFan? This looks a lot like hardcore CrankshaftScript to me.

@mscdex
Copy link
Contributor Author

mscdex commented May 23, 2017

@bmeurer I did actually try (without these changes and with variations on these improvements) with --turbo and there were significant performance regressions. I just chalked that up to TurboFan not being where it needs to be yet as of V8 5.8?

@jasnell
Copy link
Member

jasnell commented May 23, 2017

As of 5.8, yes, regressions would be expected. There is a test build available that uses 6.0 that should be tested against.

@fhinkel
Copy link
Member

fhinkel commented May 24, 2017

Here are the sources for Node with V8 6.0 (V8 master). If you tell me how to run the benchmark I can try later today. Edit: Use this

I'm not in favor of these changes:

  • Readability (obvious 😉 )
  • We have to check with every V8 update that these changes still make it faster. Most likely, these changes will go stale very quickly
  • Engine specific optimizations like this go completely against our VM Neutrality efforts, /cc @nodejs/chakracore

I suggest we check with V8 60, if there's still a performance difference. If there is, we'll investigate on the V8 side if we can help at all. If we do end up landing this, I'd like meaningful comments on each loop explaining the change. And a commitment from somebody to revisit this every time we update v8. Preferably a script that runs the benchmark with and without these patches. (This might be overly ambiguous).

@targos
Copy link
Member

targos commented May 24, 2017

And here are the sources for Node (master) with V8 6.0 (master) 😉.

@mscdex
Copy link
Contributor Author

mscdex commented May 24, 2017

@fhinkel Regarding "stale" optimizations, that's most likely always going to be the case. It's very much a cat and mouse game because V8 (and other VMs for that matter) is changing all the time to varying degrees and we do not always upgrade to the latest major V8 version as soon as they go stable. Something else to consider is that while optimizations may go "stale" in master, they could very well still be applicable for older branches that do not get major V8 upgrades.

As far as VM neutrality goes, there is no real good answer for that AFAIK. We cannot just create copies of parts of node for different VMs if we ever want to do optimizations. Has there been any discussion about this in any of the working groups or VM "meetups?"

I don't think we have the resources to run benchmarks regularly before and specific changes. We've made a lot of (and probably will in the future) changes that may/may not be V8-specific and trying to manage and test all of those would probably be a nightmare (not to mention how some of the changes may interact with each other...).

@simonkcleung
Copy link

Maybe you dont understand what I mean (or I dont know something).
In ee-emit.js, it repeatedly do ee.emit('dummy') many times. But with your changes, for example,

 switch (len) {
      case 1: fn.call(this); break;
      case 2: fn.call(this, arguments[1]); break;
      case 3: fn.call(this, arguments[1], arguments[2]); break;
      case 4: fn.call(this, arguments[1], arguments[2], arguments[3]);

only case 1 will run, and cases 2, 3, 4 will never.

@mscdex
Copy link
Contributor Author

mscdex commented May 24, 2017

@simonkcleung There is a separate benchmark for multiple arguments. The two could probably be merged together at some point I suppose. However the performance improvements should be similar for those other cases.

fn[3].call(this);
if (fnlen === 4) break;
fn[4].call(this);
if (fnlen === 5) break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (fnlen === 5) [](start = 7, length = 17)

Is there any significance of unrolling the loop for 5 ? Or is it that in most common scenario there are not more than 5 event handlers to trigger? Either case, could you write a comment about it?

@kunalspathak
Copy link
Member

we've seen in events lately that will hopefully be fixed once the v8 patch lands

@jasnell - Could you please elaborate what v8 patch you are mentioning? Were there changes in inline heuristics of v8 that regressed events?

@jasnell
Copy link
Member

jasnell commented May 30, 2017

@kunalspathak ... the patch was a performance improvement to Array.prototype.shift/unshift. I don't have the specific PR handy, but it landed in core and the performance regression I was seeing appears to have been addressed.

@mcollina
Copy link
Member

mcollina commented Jun 2, 2017

Can we check it against streams and/or http benchmarks?
The code here reads significantly worse, and I would like to be sure.

I checked some basic benchmarks I typically use to check new node releases, and I am not seeing any significant performance improvements with this branch.

@simonkcleung
Copy link

simonkcleung commented Jun 4, 2017

It seems using rest parameters is better in both readability and performance in current v8 used by node ver. 8.0.0.
Besides, as the function do nothing in the benchmarks, the performance improved in real life cases will be very small.

@mscdex
Copy link
Contributor Author

mscdex commented Jun 7, 2017

Ok I just re-tested this PR as-is with V8 5.9 which is now in master and just from a few runs the trend I'm seeing is this:

  • ee-add-remove result is only slightly lower, ~6.xx % increase
  • ee-emit results are a fair amount lower, but still in double digits: ~12% increase for 2 listeners, ~19% for 5 listeners, about the same for 10 listeners
  • ee-listeners results are actually higher than before, starting at ~25% for 2 listeners

@mscdex
Copy link
Contributor Author

mscdex commented Jun 16, 2017

Any further comments on this, given the performance details I previously posted for V8 5.9 (which uses TurboFan by default now)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
events Issues and PRs related to the events subsystem / EventEmitter. performance Issues and PRs related to the performance of Node.js.
Projects
None yet
Development

Successfully merging this pull request may close these issues.