-
Notifications
You must be signed in to change notification settings - Fork 29.9k
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: several perf related adjustments #16212
Changes from 5 commits
adabed3
75571fb
00698a6
40a7e23
0585047
05c065f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,9 +54,6 @@ Object.defineProperty(EventEmitter, 'defaultMaxListeners', { | |
return defaultMaxListeners; | ||
}, | ||
set: function(arg) { | ||
// force global console to be compiled. | ||
// see https://github.com/nodejs/node/issues/4467 | ||
console; | ||
// check whether the input is a positive number (whose value is zero or | ||
// greater and not a NaN). | ||
if (typeof arg !== 'number' || arg < 0 || arg !== arg) { | ||
|
@@ -77,7 +74,8 @@ EventEmitter.init = function() { | |
} | ||
} | ||
|
||
if (!this._events || this._events === Object.getPrototypeOf(this)._events) { | ||
if (this._events === undefined || | ||
this._events === Object.getPrototypeOf(this)._events) { | ||
this._events = Object.create(null); | ||
this._eventsCount = 0; | ||
} | ||
|
@@ -163,24 +161,21 @@ function emitMany(handler, isFn, self, args) { | |
} | ||
} | ||
|
||
EventEmitter.prototype.emit = function emit(type) { | ||
var er, handler, len, args, i, events, domain; | ||
var needDomainExit = false; | ||
var doError = (type === 'error'); | ||
EventEmitter.prototype.emit = function emit(type, ...args) { | ||
let doError = (type === 'error'); | ||
|
||
events = this._events; | ||
if (events) | ||
doError = (doError && events.error == null); | ||
const events = this._events; | ||
if (events !== undefined) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changes like this really warrant a CITGM run. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I completely agree. 👍 I tried to flag it in my description of the PR but it's a bit of a wall of text. |
||
doError = (doError && events.error === undefined); | ||
else if (!doError) | ||
return false; | ||
|
||
domain = this.domain; | ||
const domain = this.domain; | ||
|
||
// If there is no 'error' event listener then throw. | ||
if (doError) { | ||
if (arguments.length > 1) | ||
er = arguments[1]; | ||
if (domain) { | ||
let er = args[0]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is bad in case args is of length zero. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, thanks for catching it! That was never meant to make it into the PR because it's about 10x slower than checking length first. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bmeurer wouldn't it be a good idea to improve this in v8? It is somewhat weird that it is best to check for the length first instead of having v8 doing this automatically. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading out of bounds is tricky in JS and requires some machinery. But yes we should probably fix this, esp. since jQuery and Co. already do that and don't want to give up on that practice. Can you file a bug on the V8 tracker pointing to this example? |
||
if (domain !== null && domain !== undefined) { | ||
if (!er) { | ||
const errors = lazyErrors(); | ||
er = new errors.Error('ERR_UNHANDLED_ERROR'); | ||
|
@@ -203,37 +198,32 @@ EventEmitter.prototype.emit = function emit(type) { | |
return false; | ||
} | ||
|
||
handler = events[type]; | ||
const handler = events[type]; | ||
|
||
if (!handler) | ||
if (handler === undefined) | ||
return false; | ||
|
||
if (domain && this !== process) { | ||
let needDomainExit = false; | ||
if (domain !== null && domain !== undefined && this !== process) { | ||
domain.enter(); | ||
needDomainExit = true; | ||
} | ||
|
||
var isFn = typeof handler === 'function'; | ||
len = arguments.length; | ||
switch (len) { | ||
// fast cases | ||
case 1: | ||
const isFn = typeof handler === 'function'; | ||
switch (args.length) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait if we're using rest args, while the unwinding? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's still faster to have these unwound cases than just using I tested probably 6 different permutations of this, this one came out consistently the fastest. I had some that were faster for the more than 4 args case but slower for the fast cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you gist those permutations? BTW: I'm also seeing faster results for unwound calls, but <10% differance There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the difference isn't massive but I'm not comfortable having a performance regression. Will put together a gist this weekend if I can recall exactly what it was I tested. |
||
case 0: | ||
emitNone(handler, isFn, this); | ||
break; | ||
case 1: | ||
emitOne(handler, isFn, this, args[0]); | ||
break; | ||
case 2: | ||
emitOne(handler, isFn, this, arguments[1]); | ||
emitTwo(handler, isFn, this, args[0], args[1]); | ||
break; | ||
case 3: | ||
emitTwo(handler, isFn, this, arguments[1], arguments[2]); | ||
break; | ||
case 4: | ||
emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]); | ||
emitThree(handler, isFn, this, args[0], args[1], args[2]); | ||
break; | ||
// slower | ||
default: | ||
args = new Array(len - 1); | ||
for (i = 1; i < len; i++) | ||
args[i - 1] = arguments[i]; | ||
emitMany(handler, isFn, this, args); | ||
} | ||
|
||
|
@@ -254,13 +244,13 @@ function _addListener(target, type, listener, prepend) { | |
} | ||
|
||
events = target._events; | ||
if (!events) { | ||
if (events === undefined) { | ||
events = target._events = Object.create(null); | ||
target._eventsCount = 0; | ||
} else { | ||
// To avoid recursion in the case that type === "newListener"! Before | ||
// adding it to the listeners, first emit "newListener". | ||
if (events.newListener) { | ||
if (events.newListener !== undefined) { | ||
target.emit('newListener', type, | ||
listener.listener ? listener.listener : listener); | ||
|
||
|
@@ -271,7 +261,7 @@ function _addListener(target, type, listener, prepend) { | |
existing = events[type]; | ||
} | ||
|
||
if (!existing) { | ||
if (existing === undefined) { | ||
// Optimize the case of one listener. Don't need the extra array object. | ||
existing = events[type] = listener; | ||
++target._eventsCount; | ||
|
@@ -337,10 +327,7 @@ function onceWrapper() { | |
return this.listener.call(this.target, arguments[0], arguments[1], | ||
arguments[2]); | ||
default: | ||
const args = new Array(arguments.length); | ||
for (var i = 0; i < args.length; ++i) | ||
args[i] = arguments[i]; | ||
this.listener.apply(this.target, args); | ||
this.listener.apply(this.target, arguments); | ||
} | ||
} | ||
} | ||
|
@@ -385,11 +372,11 @@ EventEmitter.prototype.removeListener = | |
} | ||
|
||
events = this._events; | ||
if (!events) | ||
if (events === undefined) | ||
return this; | ||
|
||
list = events[type]; | ||
if (!list) | ||
if (list === undefined) | ||
return this; | ||
|
||
if (list === listener || list.listener === listener) { | ||
|
@@ -422,7 +409,7 @@ EventEmitter.prototype.removeListener = | |
if (list.length === 1) | ||
events[type] = list[0]; | ||
|
||
if (events.removeListener) | ||
if (events.removeListener !== undefined) | ||
this.emit('removeListener', type, originalListener || listener); | ||
} | ||
|
||
|
@@ -434,15 +421,15 @@ EventEmitter.prototype.removeAllListeners = | |
var listeners, events, i; | ||
|
||
events = this._events; | ||
if (!events) | ||
if (events === undefined) | ||
return this; | ||
|
||
// not listening for removeListener, no need to emit | ||
if (!events.removeListener) { | ||
if (events.removeListener === undefined) { | ||
if (arguments.length === 0) { | ||
this._events = Object.create(null); | ||
this._eventsCount = 0; | ||
} else if (events[type]) { | ||
} else if (events[type] !== undefined) { | ||
if (--this._eventsCount === 0) | ||
this._events = Object.create(null); | ||
else | ||
|
@@ -470,7 +457,7 @@ EventEmitter.prototype.removeAllListeners = | |
|
||
if (typeof listeners === 'function') { | ||
this.removeListener(type, listeners); | ||
} else if (listeners) { | ||
} else if (listeners !== undefined) { | ||
// LIFO order | ||
for (i = listeners.length - 1; i >= 0; i--) { | ||
this.removeListener(type, listeners[i]); | ||
|
@@ -481,23 +468,19 @@ EventEmitter.prototype.removeAllListeners = | |
}; | ||
|
||
EventEmitter.prototype.listeners = function listeners(type) { | ||
var evlistener; | ||
var ret; | ||
var events = this._events; | ||
const events = this._events; | ||
|
||
if (!events) | ||
ret = []; | ||
else { | ||
evlistener = events[type]; | ||
if (!evlistener) | ||
ret = []; | ||
else if (typeof evlistener === 'function') | ||
ret = [evlistener.listener || evlistener]; | ||
else | ||
ret = unwrapListeners(evlistener); | ||
} | ||
if (events === undefined) | ||
return []; | ||
|
||
return ret; | ||
const evlistener = events[type]; | ||
if (evlistener === undefined) | ||
return []; | ||
|
||
if (typeof evlistener === 'function') | ||
return [evlistener.listener || evlistener]; | ||
|
||
return unwrapListeners(evlistener); | ||
}; | ||
|
||
EventEmitter.listenerCount = function(emitter, type) { | ||
|
@@ -512,12 +495,12 @@ EventEmitter.prototype.listenerCount = listenerCount; | |
function listenerCount(type) { | ||
const events = this._events; | ||
|
||
if (events) { | ||
if (events !== undefined) { | ||
const evlistener = events[type]; | ||
|
||
if (typeof evlistener === 'function') { | ||
return 1; | ||
} else if (evlistener) { | ||
} else if (evlistener !== undefined) { | ||
return evlistener.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.
@bmeurer what do you think? As far as I understand, this is related to the escape analysis system. Do we need V8 62 to have the perf benefit?
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.
The escape analysis is responsible for avoiding allocations of either
arguments
object or rest parameters. If escape analysis is off, every invocation of this function will always have to allocatearguments
or rest parameters, even if it's not used.So, yes, for code like this, independent of whether you use
arguments
or rest parameters, escape analysis usually helps performance-wise.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.
What is the latest on Escape Analysis in 8.x LTS? I know there was an issue opened discussing it but can't tell by looking at it what was decided, if anything yet.