Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Improve performance of event and timer interfaces #9007

Closed
wants to merge 5 commits into from
Closed

Improve performance of event and timer interfaces #9007

wants to merge 5 commits into from

Conversation

RubenVerborgh
Copy link

This pull request speeds up multi-listener event callbacks, setImmediate, setTimeout, and setInterval by differentiating between zero-, one-, and two-argument callbacks. This technique was previously only applied to single-listener event callbacks—and even they have been sped up.

Current situation

The EventEmitter#emit code has special cases for zero-, one-, and two-argument callbacks as follows:

len = arguments.length;
switch (len) {
  // fast cases
  case 1: handler.call(this); break;
  case 2: handler.call(this, arguments[1]); break;
  case 3: handler.call(this, arguments[1], arguments[2]); break;
  // slower
  default:
    args = new Array(len - 1);
    for (i = 1; i < len; i++)
      args[i - 1] = arguments[i];
    handler.apply(this, args);
}

Improvements in this pull request

On the one hand, this pull request speeds up this technique by avoiding the use of arguments indexing, instead adding actual function arguments arg1 and arg2.

On the other hand, this pull request also applies the same technique to listeners with multiple callbacks, as well as the timer functions setImmediate, setTimeout, and setInterval.

Below are my results of performance tests that examine these cases (ran on a 2010 MacBook Pro).

v0.10.29 ← diff → @master ← diff → #9007
event with one listener 1.341 ms +298% 5.337 ms -54,11% 2.449 ms
event with two listeners 2.868 ms +70% 4.872 ms -44,33% 2.712 ms
setImmediate 21.079 ms -73% 5.786 ms -37,28% 3.629 ms
setTimeout 7.377 ms -2% 7.229 ms -50,84% 3.554 ms
setInterval 11.706 ms -44% 6.518 ms -50,43% 3.231 ms

Note how the current master branch has worsened the performance of event listeners in comparison to v0.10, and this pull request compensates for that difference. In cases where the master branch improved performance, this pull request improves it even more.

Considerations

Given the importance of callbacks in Node, delivering maximum performance for common cases is crucial. The only drawback of this pull request seems to be the increased code length; but it brings consistency since the optimization technique was previously only applied to one particular case (and slightly suboptimally).

I added each change in a different commit, so you can decide which ones to merge. All commits are independent of each other, except for the second (ca3a08d), which depends on the first (e4d4c98).

If you want these commits squashed, or applied to a different branch, please let me know and I'll do so.

This speeds up argument access
when there are 1 or 2 event arguments.
Previously, only the case with 1 listener
was optimized for less than 3 event arguments.
This is now extended to any number of listeners.
Unlike event callbacks, setImmediate
was not optimized for less than 3 arguments.
This optimization improves its performance.
Unlike event callbacks, setTimeout
was not optimized for less than 3 arguments.
This optimization improves its performance.
Unlike event callbacks, setInterval
was not optimized for less than 3 arguments.
This optimization improves its performance.
@jasnell
Copy link
Member

jasnell commented Aug 15, 2015

@RubenVerborgh ... looks like this possibly landed already in io.js (and therefore nodejs/node). Going to close it here. If necessary we can consider backporting to v0.12. Let me know if I missed something, however, and we can reopen.

@jasnell jasnell closed this Aug 15, 2015
@RubenVerborgh
Copy link
Author

Yes, this landed already in io.js; backporting would not be necessary. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants