Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34.4k
events: optimize various functions#601
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
Uh oh!
There was an error while loading. Please reload this page.
Conversation
lib/events.js Outdated
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.
listener.listener feels awkward, can the initial naming of this be tweaked at all?
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.
Maybe, I just left it as it was before. What do you suggest?
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.
@mscdex Can you put braces around the consequent?
quantizor commented Jan 26, 2015
General comment: There's a lot of |
lib/events.js Outdated
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.
at this line events is undefined, isn't it?
events variable can be initiated with
varevents=this._events;and here can be used like this:
if(!events)events=this._events={};existing=events[type];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.
No, it is defined there. I have the separate else there because of the code following this line (events.newListener can't possibly exist if this._events isn't set, so there's no need to check in that case).
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.
oh, I see, I was misled by the line nr. 137, was expecting of an equality check, actually using an assignment in an if statement is a bad practice. Would you mind to try the code that I proposed in my first comment and check the performance impact of the changes made?
mscdex commented Jan 26, 2015
I'm not sure what you mean by "significantly different?" The checks are still the same checks, except cached versions are used where possible (in addition to inline assignment). |
Fishrock123 commented Jan 28, 2015
Benchmarks do look good. (+ Tests pass.) Before: After: |
lib/events.js Outdated
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.
undefined is not re-definable in ES5. Is there a good reason to use void 0 over it?
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.
It's what util.isUndefined() was using.
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.
+0.1 for 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.
+1 for undefined
Fishrock123 commented Jan 28, 2015
Mostly LGTM, but I'd like to see other potential comments. |
micnic commented Jan 28, 2015
For me it also looks good, except the variable assignments inside if statements like @yaycmyk said, they are misleading while reading the code, the cached values should be assigned somewhere before the if statement as I proposed in this discussion |
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 suppose Object.keys() could have a negative impact on EventEmitter objects with many events because of the array it creates (instead of iterating over an object like for..in does.) Probably an uncommon case but it might be good to capture it in a benchmark.
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 suppose we could manually keep track of the length of this._events and use some length value as the cut-off between using Object.keys() and using a for-in?
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 based on false assumption, V8 for-in eagerly creates an array of the properties before the loop body is even entered. In optimized case this array is cached but that's not the case for events anyway.
So basically you never want to use a for-in, even in the case where you need prototype properties you are better off doing it manually with Object.keys and Object.getPrototypeOf (which could be turned into a function getInheritedKeys to make it fast idiom everywhere)
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.
V8 for-in eagerly creates an array of the properties before the loop body is even entered.
That's only the case for "uncommon" objects, like object proxies or objects with interceptors. The objects that EventEmitter creates are simple objects with an enum cache that for..in iterates over.
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's only the case for "uncommon" objects, like object proxies or objects with interceptors. The objects that EventEmitter creates are simple objects with an enum cache that for..in iterates over.
A normalized object is also an uncommon object which this._events definitely is as there is a delete call on it literally 18 lines below.
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.
so, maybe we should consider getting reed of deletes ?
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.
@vkurchatkin That was tried in the past in the middle of v0.10 (unfortunately) which caused memory leaks (especially if you keep using uniquely named 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.
That's not the point, this._events is being treated like a hash table - if it's not normalized by delete it will be soon normalized by adding enough event names on it (16 the last time I checked was the limit on the heuristic). Just forget for-in forever and be happy, you know that Object.keys uses the enum cache as well, right?
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.
you know that Object.keys uses the enum cache as well, right?
It still creates a new array every time though.
What you say about delete obj.key and for..in is true for Crankshaft but not TurboFan. I'm not sure what we should be optimizing for. Crankshaft is still the default but it's effectively in maintenance mode. TurboFan is going to replace it some day but that may still be far off. Or it may be next month, I don't know. :-(
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.
Object normalization is not related to crankshaft and you cannot do a simple map check with normalized objects to see if a property was deleted during an iteration of the loop so I am sure for-in will always be very slow for normalized objects unless it's very simple body that can be analyzed to never delete properties but I am not holding my breath at all.
bnoordhuis commented Jan 28, 2015
Some comments but I like the general thrust. |
a13ac82 to f93f44aComparemscdex commented Jan 28, 2015
Ok I have made suggested changes. I also went ahead and optimized some of the other functions. The changes to I benchmarked All tests still pass. EDIT: Ok, it seems now that for some reason if the array is large enough (~>=50 elements in my testing), |
f93f44a to d292fd2Comparemscdex commented Jan 29, 2015
Ok I made a tweak to the |
Fishrock123 commented Jan 29, 2015
Hmm, the new changes are slightly slower on the ee-add-remove benchmark. Unpatched: Previous patch: Current patch: |
Fishrock123 commented Jan 29, 2015
@mscdex where are you getting your numbers from? it may be prudent to write additional benchmarks for these. :) |
mscdex commented Jan 29, 2015
@Fishrock123 I did write additional benchmarks to test the other functions, but was going to submit them in a separate PR. However I did not change ee-add-remove. The results for me are the same. Not a whole lot changed in the add/remove functions anyway, merely style changes. |
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.
always include braces for conditionals please
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.
nope, one line body is ok without braces
d292fd2 to 05da7a7Comparemscdex commented Jan 29, 2015
I've also now made some improvements to Benchmark code: varBenchmark=require('benchmark');varee=new(require('events').EventEmitter)();for(vark=0;k<10;k+=1)ee.on('dummy',function(){});varsuite=newBenchmark.Suite();suite.add('emitter#emit',function(){// tested code},{minSamples: 100});suite.on('cycle',function(event){console.log(String(event.target));});suite.run({async: false});0 args testee.emit('dummy');Results:
1 arg testee.emit('dummy',1);Results:
2 args testee.emit('dummy',1,false);Results:
3 args testee.emit('dummy',1,false,null);Results:
4 args test(this hits the slow case code) ee.emit('dummy',1,false,null,'foo');Results:
|
tjconcept commented Jan 31, 2015
If performance is a priority in iojs/io.js, I think @petkaantonov is the guy. He has done some amazing work by replacing node core stuff with faster (as in 100x) user land versions. |
mscdex commented Feb 1, 2015
@tjconcept There is already #643 for incorporating his url module. Anyway, I think these changes are a good start. |
mscdex commented Feb 1, 2015
@bnoordhuis rebased. |
bnoordhuis commented Feb 2, 2015
@mscdex It seems to break parallel/test-event-emitter-add-listeners and message/stdin_messages: |
mscdex commented Feb 2, 2015
That's strange ... I did a clean |
bnoordhuis commented Feb 2, 2015
I landed PR #687 earlier today. It's possible that it's interacting badly with this PR. |
4107f84 to faf4841Comparemscdex commented Feb 3, 2015
Ok, tests now pass again :-) |
lib/events.js Outdated
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.
a comment above these might be nice.
Fishrock123 commented Feb 5, 2015
ee-add-remove consistently sees an 80+% gain with this patch. LGTM. @trevnorris Can you take a look since you were doing #533? (Side-note: we need some |
mscdex commented Feb 5, 2015
RE: additional benchmarks, here are some that I created during my testing FWIW. |
Fishrock123 commented Feb 5, 2015
Mind PR-ing those soon? :) |
mscdex commented Feb 5, 2015
@Fishrock123 Done: #730. |
Cache events and listeners objects where possible and loop over Object.keys() instead of using for..in. These changes alone give ~60-65% improvement in the ee-add-remove benchmark. The changes to EventEmitter.listenerCount() gives ~14% improvement and changes to emitter.listeners() gives significant improvements for <50 listeners (~195% improvement for 10 listeners). The changes to emitter.emit() gives 3x speedup for the fast cases with multiple handlers and a minor speedup for the slow case with multiple handlers. The swapping out of the util.is* type checking functions with inline checks gives another ~5-10% improvement.
faf4841 to bf8d55cComparetrevnorris commented Feb 6, 2015
Haven't taken the time to run the tests myself, but the code LGTM. |
bnoordhuis commented Feb 6, 2015
Fishrock123 commented Feb 6, 2015
Some results of the new benchmarks added in 847b9d2 with the iterations in #746 (I frequently am getting a 5-10% regression on that ee-listeners-many.js benchmark) |
mscdex commented Feb 6, 2015
Yeah, I wasn't quite sure about the heuristics that I used for |
bnoordhuis commented Feb 8, 2015
I'm willing to chalk up the events/ee-listeners-many regression as a fluke. The benchmark seems to do the same amount of work with and without this pull request applied: memory usage is about the same, output of @Fishrock123@trevnorris Thoughts? |
bnoordhuis commented Feb 9, 2015
LGTM, for the record. Can I get one more @iojs/collaborators LGTM? |
evanlucas commented Feb 9, 2015
LGTM |
Cache events and listeners objects where possible and loop over Object.keys() instead of using for..in. These changes alone give ~60-65% improvement in the ee-add-remove benchmark. The changes to EventEmitter.listenerCount() gives ~14% improvement and changes to emitter.listeners() gives significant improvements for <50 listeners (~195% improvement for 10 listeners). The changes to emitter.emit() gives 3x speedup for the fast cases with multiple handlers and a minor speedup for the slow case with multiple handlers. The swapping out of the util.is* type checking functions with inline checks gives another ~5-10% improvement. PR-URL: #601 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
bnoordhuis commented Feb 9, 2015
Thanks Brian, landed in b677b84. |
Cache events and listeners objects where possible and loop over
Object.keys() instead of using for..in. These changes alone give
~60-65% improvement in the ee-add-remove benchmark.
The swapping out of the util type checking functions with inline
checking gives another ~5-10% improvement.