-
Notifications
You must be signed in to change notification settings - Fork 30k
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
EventEmitter: use a Map to store event listeners #1785
Conversation
In the results you posted Map is always performing worse? |
else | ||
dest._events.error = [onerror, dest._events.error]; | ||
dest._events.set('error', [onerror, dest._events.get('error')]); |
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 believe all of these changes to readable stream are going to result in the same problem as when I proposed optimizations for eventEmitter.once()
(#914), which is that some modules on npm have their readable-stream
pinned to specific versions or limited version ranges. So if someone uses an older readable-stream
on an io.js with these changes, things will break.
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 That would be a lot of someones.
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.
Last I checked npm (a few weeks ago) it was only about a dozen or so? I had actually thought about submitting PRs to those projects just so we could get the .once()
optimizations.
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.
Btw, there are other modules that are using _events
directly. For example:
- https://github.com/mscdex/dicer/blob/master/lib/Dicer.js#L91
- https://github.com/mscdex/busboy/blob/master/lib/types/multipart.js#L183
- https://github.com/unshiftio/ultron/blob/master/index.js#L79
That's in my current node_modules
dir, except for the readable-stream
.
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.
@ChALkeR I was referring more generally about changes to readable-stream
and how modules are depending on it version-wise, not specific code changes. But yes, there are probably a ton of modules that use _events
directly.
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.
Last I checked npm (a few weeks ago) it was only about a dozen or so?
Just in my current node_modules
dir I have 19 of those. List:
bufferstreams "readable-stream": "^1.0.33"
tar-stream "readable-stream": "~1.0.33",
dicer "readable-stream": "1.1.x",
duplexer2 "readable-stream": "~1.1.9"
through2 "readable-stream": ">=1.0.33-1 <1.1.0-0",
are-we-there-yet "readable-stream": "^1.1.13"
decompress-zip "readable-stream": "^1.1.8",
tar-pack "readable-stream": "~1.0.2",
bl "readable-stream": "~1.0.26"
readdirp "readable-stream": "~1.0.26-2"
busboy "readable-stream": "1.1.x"
raw-body "readable-stream": "~1.0.33",
gulp-bower/through2 "readable-stream": ">=1.0.28 <1.1.0-0",
htmlparser2 "readable-stream": "1.1"
concat-stream "readable-stream": "~1.1.9",
mongodb "readable-stream": "1.0.31"
read-all-stream "readable-stream": "~1.1.13"
finalhandler "readable-stream": "~1.0.33",
duplexify "readable-stream": "^1.1.13"
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.
If this gets merged, will 1.0.* and 1.1.* branches of readable-stream receive patches?
If yes, it is true that there are not so many packages that specify a fixed version of readable-stream as a dependency.
@petkaantonov |
The benchmark gives results in op/s (higher is better). |
if (events.removeListener) | ||
this.emit('removeListener', type, listener); | ||
} | ||
events.delete(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.
I'm curious about performance differences with this particular change. Is it faster to keep _eventsCount
around and create a new Map()?
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 is no significant performance difference if I revert ca75072. I tested with this._events.clear()
and this._events = new Map()
Ah, that's the bundled benchmark from io.js/benchmark/events, I missed that. Something is not right here then. |
@@ -2914,9 +2914,6 @@ void SetupProcessObject(Environment* env, | |||
env->SetMethod(process, "_setupNextTick", SetupNextTick); | |||
env->SetMethod(process, "_setupPromises", SetupPromises); | |||
env->SetMethod(process, "_setupDomainUse", SetupDomainUse); | |||
|
|||
// pre-set _events object for faster emit checks | |||
process->Set(env->events_string(), Object::New(env->isolate())); |
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.
Is this necessary? Isn't there a way to create a Map from C++?
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 couldn't find anything related to maps in the v8 API and I know that the Map constructor is written in JS but there may be a way to call it from C++
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 see the NativeWeakMap
but I can't find the regular Map either.
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 will be possible with v8 4.5: v8/v8@395fa8b
@petkaantonov is right, I assumed it was the time... |
ES6 Map is slower than Object props in V8. And EventEmitter is our core module, if event emitter is slower, whole system may be slow on performance. |
@yosuke-furukawa This seems to depend on the usecase. I saw usecases where replacing objects with Maps resulted in a 2x speedup. |
@ChALkeR Of course, the benchmark result is depend on the usecase. But current result seems to be worse. @petkaantonov is correct. our benchmark test shows |
@yosuke-furukawa, Yes, I know about the bundled benchmarks. |
Is prefixing events something that we can do without breaking anything? I think that would be a much better alternative. Map simply isn't fast enough yet. |
@yosuke-furukawa @Fishrock123 I agree that the current perf of Map is blocking this. |
It's going to be an uphill battle to directly change the format of _events, as @mscdex noted, at minimum making sure that both minor versions of readable-stream get a backwards-compatible patch release, and propagating that through the package ecosystem. If the perf improves, I'd advise presenting a legacy _events interface via a getter and putting the map-backed interface behind a symbol or weakmap.
|
Btw, about the Samples:
It looks to me that there should be methods in
@mscdex, thoughts? |
@ChALkeR Using |
@mscdex That's why I marked that as (optional). |
@ChALkeR Well, for that you could iterate over |
|
0921fb9
to
66518a0
Compare
Not to defend ultron - whatever it is - but introspecting an EE to see all the events that have been listened on would be very useful for debugging. For example, to check if a misspelled event name is being used. I'd like to see node be more introspectable, so if getting this info causes zero additional overhead, I think it'd be nice to have. |
👍 to allowing more introspection of event emitters. |
@@ -0,0 +1,3 @@ | |||
'use strict'; | |||
|
|||
exports.EE_events = Symbol('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.
Use camelCase.
@jasnell I rebased on master. here are the new benchmark numbers:
|
I guess I can close this PR for now. I don't think it will improve in a near future. |
@targos This also fixes another bug in the implementation. As of now, if we use inherited members as event names, it will confuse the events module a little. So I kinda hoped that this would land :( |
@targos ... Given the current status on this and your last comment, I'm inclined to close. Per @thefourtheye's comment tho, there may still be some stuff worth saving in this so perhaps another look is warranted? Particularly with the newer V8 in master? |
OK, closing for now. @thefourtheye I think the problem you are talking about can be fixed by using |
the performance tests were run in 2012. I would try with current V8 version to be sure it is still a problem. |
@targos I wrote this simple script to compare the performance of them 'use strict';
const times = 10e7;
console.time('Object.create');
for (var i = 0; i < times; i += 1) {
let obj = Object.create(null);
}
console.timeEnd('Object.create');
console.time('Object Literal');
for (var i = 0; i < times; i += 1) {
let obj = {};
}
console.timeEnd('Object Literal');
|
This is a fix for #728.
I ran the events benchmarks to compare this with master and there seems to be a significant performance drop for some cases (though I'm not sure of how I should compare the runs).