-
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
Improve performance of event and timer interfaces #406
Conversation
+10 |
@RubenVerborgh: It's somewhat interesting that setImmediate seems to be slower than in nodejs. Did you stumble on anything while researching this that would explain why? |
@jbergstroem I've only looked at the JavaScript level, which has remained unchanged. It must have something to do with one of the underlying layers. Note that setImmediate was quite slow in Node 0.10.x, so something has happened to make it faster in the current master. |
Thinking about this some more, it seems that io.js did not implement the patch that made one-listener events in Node slower after v0.10.x. I wonder if that same patch is responsible for the other differences. |
Rebased on v1.x to resolve a merge conflict. |
* those args. The overhead of an extra closure is not | ||
* desired in the normal case. | ||
*/ | ||
var args = Array.prototype.slice.call(arguments, 2); |
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.
This is a typical de-opt; I wonder how much difference it makes already not having this.
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.
There will already be some difference for sure. But why stop there, if the other cases show an improvement by using the switch as well?
SGTM Little odd to me that |
so you are somewhat like this way: https://github.com/XadillaX/Toshihiko/blob/develop/util/damnarguments.js and https://github.com/XadillaX/Toshihiko/blob/develop/util/damnargumentsgenerator.js. maybe you can try more arguments. |
@Fishrock123 Slower? @XadillaX 2 seems like a good number to stop. Events with more arguments are rare. Plus there is a slight overhead in allocating the variables, as they are used in a closure. (Also, I'd suggest to convert the long chain of |
I mean slower than |
@Fishrock123 No meaningful comparison is possible between different tests; they only allow comparing the performance of different io.js versions for the same test. The number of repetitions for each test was chosen such that their timings were roughly in the 1–10 second range. |
for (i = 1; i < len; i++) | ||
args[i - 1] = arguments[i]; | ||
|
||
} else if (util.isArray(handler)) { |
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.
switch to Array.isArray()
?
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.
Fine with me; is there a guideline for such decisions in general? I followed the existing convention in the file.
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.
I just noted during rebasing that the conflicts are due to code changes moving away from util
operations. So I'll do this in general.
I like what this PR is getting at and I'd certainly like to see it merged in. Looks like it needs a rebase though. |
Here you go, rebased on v1.x. |
…and rebased again. @mscdex might want to check whether the optimizations from b677b84 are relevant here as well. One of my original commits was not necessary anymore after b677b84. |
repeat *= 1; // coalesce to number or NaN | ||
|
||
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) { | ||
repeat = 1; // schedule on next tick, follows browser behaviour | ||
} | ||
|
||
var timer = new Timeout(repeat); | ||
var args = Array.prototype.slice.call(arguments, 2); | ||
var timer = new Timeout(repeat), len = arguments.length - 2, args, i; |
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.
could you put the unassigned variables (args, i) in front of the assigned ones?
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.
Done.
Right now the code looks good to me but I have yet to run tests (with the libuv issue for 1.4 I can't properly test) so I'm not gonna officially sign off on this until tomorrow when they have that fixed. By the way, I think we'd be happy to have your benchmarks added into the repository. I'd shepherd in a pull request for those if you'd be willing to patch them into how we do them here. @Fishrock123 was your SGTM a sign-off or just a +1? |
I got the tests to pass. However I'm not really seeing any improvement on the |
Confirmed that the event emitter improvements no longer make a difference, due to b677b84. Will remove the commit in question if needed. |
Yeah, if the commit no longer makes a difference I'd remove it. I'll run your benches for the timers to test performance before I give my official sign off. Since this pull request deals with timers, a locked API, I can't be the sole signee to be sure this wouldn't have consequences I don't see. Summoning @iojs/collaborators |
exports.setTimeout = function(callback, after) { | ||
var timer; | ||
exports.setTimeout = function(callback, after, arg1, arg2, arg3) { | ||
var timer, i, args, len = arguments.length; |
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.
I prefer var len = arguments.length;
on a different line. single assignation per line, and all unused variable on another. :)
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.
Done.
Left a couple comments, and the commits squashed, and their messages cleaned up, but LGTM otherwise. |
@trevnorris Please detail what you mean with “messages cleaned up”. Just one commit then with “Improve timer performance?” |
@RubenVerborgh Squashed, the message should read |
Squashed and commit message edited. |
var immediate = new Immediate(); | ||
var args, index; | ||
var i, args, len = arguments.length; |
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.
len = arguments.length
on it's own line?
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.
Done.
One comment, otherwise LGTM. |
This pull request speeds up multi-listener event callbacks,
setImmediate
,setTimeout
, andsetInterval
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: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 argumentsarg1
andarg2
.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
, andsetInterval
.Below are my results of performance tests that examine these cases (ran on a 2010 MacBook Pro).
Considerations
Given the importance of callbacks in io.js, 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, which depends on the first.
If you want these commits squashed, or applied to a different branch, please let me know and I'll do so.
PS This pull request is the equivalent of nodejs/node-v0.x-archive#9007.