Uh oh!
There was an error while loading. Please reload this page.
events: optimize various functions - #601
Conversation
There was a problem hiding this comment.
listener.listener feels awkward, can the initial naming of this be tweaked at all?
There was a problem hiding this comment.
Maybe, I just left it as it was before. What do you suggest?
quantizor
commented
Jan 26, 2015
General comment: There's a lot of |
There was a problem hiding this comment.
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.
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.
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: |
There was a problem hiding this comment.
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.
It's what util.isUndefined() was using.
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.
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.
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.
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.
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.
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.
so, maybe we should consider getting reed of deletes ?
There was a problem hiding this comment.
@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.
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.
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.
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.
always include braces for conditionals please
There was a problem hiding this comment.
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 :-) |
There was a problem hiding this comment.
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. |
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 <info@bnoordhuis.nl> Reviewed-By: Evan Lucas <evanlucas@me.com>
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.