-
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
events: make eventNames() use Reflect.ownKeys() #5822
Conversation
Use `Reflect.ownKeys()` instead of `Object.keys()` and `Object.getOwnPropertySymbols()`.
See discussion on #5818. |
I believe this would be our first use of |
@mscdex using your benchmark, I've tried adding symbol properties to the 'use strict';
var common = require('../common.js');
var EE = require('events');
var v8 = require('v8');
var bench = common.createBenchmark(main, { n: [5e4] });
function main(conf) {
var n = conf.n | 0;
var ee = new EE();
for (var k = 0; k < 1000; k += 1) {
ee.on('dummy' + k, function() {});
ee.on(Symbol(), function() {});
}
// Force optimization before starting the benchmark
ee.eventNames().length;
v8.setFlagsFromString('--allow_natives_syntax');
eval('%OptimizeFunctionOnNextCall(ee.eventNames)');
ee.eventNames().length;
bench.start();
for (var i = 0; i < n; i += 1) {
ee.eventNames().length;
}
bench.end(n);
} Results are interesting:
I think symbols are not widely used as event names but maybe it is still something to consider. |
@lpinca I'm not too worried about that as I'd rather take a -24% hit on an edge case like that than a -89% hit in a more common case. |
Yeah ofc. |
Anyway, is having a fixed set of event names the most common use case? Maybe, I don't know, but in that case why would I need to run We should add a benchmark to compare the two solution when events are added or removed between |
Does it make sense to add a new event with a given probability in the for (var i = 0; i < n; i += 1) {
if (Math.random() < 0.1)
ee.on('foo' + i, function() {});
ee.eventNames().length;
} If so, I get these results with the above change (no symbols on initialization):
|
Well, It might be fair to cache the result in a for(var i = 0; i < Object.keys(o).length ; i++) {
doSomethingWith(Object.keys(o)[i]) I see plenty of people doing: const keys = Object.keys(o);
for(var i = 0; i < keys.length; i++) {
doSomethingWith(keys[i]) It's worth mentioning that from v8's side this optimization is entirely reasonable for objects that are not used as dictionaries or mutate very rarely - this is not the case here. |
That said - if other collaborators feel like the use case is common enough or don't feel entirely comfortable with this change in terms of performance - I'm also fine with leaving things the way they are. |
@mscdex your opinion would be appreciated here - I want to either land or close this and to do either we need consensus :) |
Can you guys give a comment on #5822 (comment)? |
@lpinca in my opinion the benchmark isn't indicative of usage because when people run over the keys they always cache it - I've literally never seen anyone write: for(var i = 0; i < Object.keys(o).length ; i++) {
doSomethingWith(Object.keys(o)[i]) And lots of times people write: const keys = Object.keys(o);
for(var i = 0; i < keys.length; i++) {
doSomethingWith(keys[i]) Which means that the reason Object.keys is faster (caching of the structure at the v8 level) is irrelevant. However, I acknowledge I'm not aware of every use case - and @mscdex has objected to the performance regression before so I want to know if the new code (and benchmarks) satisfy him. Stylistically I like the change. |
Stylistically, LGTM. But if the performance hit is really 89%, then -1. |
@cjihrig the performance hit is ~19% in the benchmark above. I think in practice it's probably a lot less than that. |
@cjihrig please take a look at the benchmark where perf is -89%. It compares two implementations where one caches result (internally) and one don't. It's pretty obvious that the former wins. What @benjamingr is saying, I guess, is that realistically if you know beforehand that keys will not change there is no point in calling const events = ee.eventNames(); If keys will change, then this implementation is faster. With the above benchmark, new keys are added with a probability of 10% and this make the implementation in question 19% faster. To reiterate, I'm not sure if this invalidate the original benchmark @mscdex wrote. Also I'm obviously biased here. |
I'll defer to @mscdex here. I'm all for simplifying the code. |
Out of curiosity, what is the performance like (both with the |
I guess it really depends on how frequently this flag is set/unset. The question is, how frequently would you set this "mutated" flag to make the benchmark realistic? |
@mscdex ... any further thoughts on this one? |
@jasnell No. |
@mscdex ... I'm trying to parse the conversation around this... where did this change end up in terms of performance impact? Is it faster or slower than the current code? |
I'll sum up the discussion:
|
Given the discussion I'm inclined to leave the code as it is currently and revisiting this later if necessary. As a new API, this is not yet in broad use yet, as such, the perf impact is going to be hard to measure. Let's hold off and let this circulate in the ecosystem a bit longer before deciding if/how/when to optimize it. |
I guess it's hard to say how this new API will be used but I think in the most common use case this method will only be called once making the current and proposed solutions equivalent in terms of perfomance. |
I tend to agree. Anyone wanting to track mutations already have events they can listen to. It's unlikely that this method will be in very many hot code paths. |
@lpinca ... how about we close this PR for now and revisit it again later if necessary? |
@jasnell I don't mind but in that case why don't we go for this one-liner? Advantages:
Disadvanges:
I would like to have more people weigh in, but again feel free to close it. |
Yeah, you're right. There's no harm and it's less code ;-) |
LGTM |
@cjihrig ... does this still LGTY? |
Yea, I suppose so. |
Use `Reflect.ownKeys()` instead of `Object.keys()` and `Object.getOwnPropertySymbols()`. PR-URL: #5822 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Landed in c1cd644 |
Use `Reflect.ownKeys()` instead of `Object.keys()` and `Object.getOwnPropertySymbols()`. PR-URL: nodejs#5822 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Use `Reflect.ownKeys()` instead of `Object.keys()` and `Object.getOwnPropertySymbols()`. PR-URL: #5822 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Pull Request check-list
Please make sure to review and check all of these items:
make -j8 test
(UNIX) orvcbuild test nosign
(Windows) pass withthis change (including linting)?
test (or a benchmark) included?
existing APIs, or introduces new ones)?
Affected core subsystem(s)
events
Description of change
Use
Reflect.ownKeys()
instead ofObject.keys()
andObject.getOwnPropertySymbols()
.