From 8366e7e5e8574c2daf3796744b33806840c4be2b Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 23 May 2017 08:43:48 -0400 Subject: [PATCH 1/3] events: improve listeners() performance --- benchmark/events/ee-listeners-many.js | 21 --------------------- benchmark/events/ee-listeners.js | 8 +++++--- lib/events.js | 17 +++++++++++++---- 3 files changed, 18 insertions(+), 28 deletions(-) delete mode 100644 benchmark/events/ee-listeners-many.js diff --git a/benchmark/events/ee-listeners-many.js b/benchmark/events/ee-listeners-many.js deleted file mode 100644 index 063732e1facb4b..00000000000000 --- a/benchmark/events/ee-listeners-many.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; -var common = require('../common.js'); -var EventEmitter = require('events').EventEmitter; - -var bench = common.createBenchmark(main, {n: [5e6]}); - -function main(conf) { - var n = conf.n | 0; - - var ee = new EventEmitter(); - ee.setMaxListeners(101); - - for (var k = 0; k < 100; k += 1) - ee.on('dummy', function() {}); - - bench.start(); - for (var i = 0; i < n; i += 1) { - ee.listeners('dummy'); - } - bench.end(n); -} diff --git a/benchmark/events/ee-listeners.js b/benchmark/events/ee-listeners.js index e91ca5078f5f98..aca70effd7686c 100644 --- a/benchmark/events/ee-listeners.js +++ b/benchmark/events/ee-listeners.js @@ -2,15 +2,17 @@ var common = require('../common.js'); var EventEmitter = require('events').EventEmitter; -var bench = common.createBenchmark(main, {n: [5e6]}); +var bench = common.createBenchmark(main, {n: [5e6], listeners: [10, 100]}); function main(conf) { var n = conf.n | 0; + var listeners = conf.listeners | 0; var ee = new EventEmitter(); + ee.setMaxListeners(listeners + 1); - for (var k = 0; k < 10; k += 1) - ee.on('dummy', function() {}); + for (var k = 0; k < listeners; k += 1) + ee.on('dummy', function() { return 0; }); bench.start(); for (var i = 0; i < n; i += 1) { diff --git a/lib/events.js b/lib/events.js index 1609d66192e333..d3b62b2b5b87b2 100644 --- a/lib/events.js +++ b/lib/events.js @@ -504,9 +504,18 @@ function arrayClone(arr, n) { } function unwrapListeners(arr) { - const ret = new Array(arr.length); - for (var i = 0; i < ret.length; ++i) { - ret[i] = arr[i].listener || arr[i]; + const first = arr[0].listener || arr[0]; + const second = arr[1].listener || arr[1]; + const n = arr.length; + switch (n) { + case 2: return [first, second]; + case 3: return [first, second, arr[2].listener || arr[2]]; + case 4: return [first, second, arr[2].listener || arr[2], + arr[3].listener || arr[3]]; + default: + const copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i].listener || arr[i]; + return copy; } - return ret; } From d3e3ade0bf0017f429c6b09573c640537a8546bb Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 23 May 2017 08:44:47 -0400 Subject: [PATCH 2/3] events: improve on() performance --- benchmark/events/ee-add-remove.js | 18 ++++++++++-------- lib/events.js | 8 ++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/benchmark/events/ee-add-remove.js b/benchmark/events/ee-add-remove.js index 99d85367cb8d6f..e1bef0b9aa314d 100644 --- a/benchmark/events/ee-add-remove.js +++ b/benchmark/events/ee-add-remove.js @@ -2,24 +2,26 @@ var common = require('../common.js'); var events = require('events'); -var bench = common.createBenchmark(main, {n: [25e4]}); +var bench = common.createBenchmark(main, {n: [25e4], listeners: [10]}); function main(conf) { var n = conf.n | 0; + var listeners = conf.listeners | 0; var ee = new events.EventEmitter(); - var listeners = []; + ee.setMaxListeners(listeners + 1); + var fns = []; var k; - for (k = 0; k < 10; k += 1) - listeners.push(function() {}); + for (k = 0; k < listeners; k += 1) + fns.push(function() { return 0; }); bench.start(); for (var i = 0; i < n; i += 1) { - for (k = listeners.length; --k >= 0; /* empty */) - ee.on('dummy', listeners[k]); - for (k = listeners.length; --k >= 0; /* empty */) - ee.removeListener('dummy', listeners[k]); + for (k = listeners; --k >= 0; /* empty */) + ee.on('dummy', fns[k]); + for (k = listeners; --k >= 0; /* empty */) + ee.removeListener('dummy', fns[k]); } bench.end(n); } diff --git a/lib/events.js b/lib/events.js index d3b62b2b5b87b2..5ca9f2bb1a135a 100644 --- a/lib/events.js +++ b/lib/events.js @@ -86,9 +86,10 @@ EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { }; function $getMaxListeners(that) { - if (that._maxListeners === undefined) + const maxListeners = that._maxListeners; + if (maxListeners === undefined) return EventEmitter.defaultMaxListeners; - return that._maxListeners; + return maxListeners; } EventEmitter.prototype.getMaxListeners = function getMaxListeners() { @@ -230,7 +231,6 @@ EventEmitter.prototype.emit = function emit(type) { }; function _addListener(target, type, listener, prepend) { - var m; var events; var existing; @@ -275,7 +275,7 @@ function _addListener(target, type, listener, prepend) { // Check for listener leak if (!existing.warned) { - m = $getMaxListeners(target); + const m = $getMaxListeners(target); if (m && m > 0 && existing.length > m) { existing.warned = true; const w = new Error('Possible EventEmitter memory leak detected. ' + From 9c7d3b0eb0e20e6aabea577b5b53cc22c4d81fd4 Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 23 May 2017 08:45:10 -0400 Subject: [PATCH 3/3] events: improve emit() performance --- benchmark/events/ee-emit.js | 8 +- lib/events.js | 180 +++++++++++++++++--------------- test/message/stdin_messages.out | 8 +- 3 files changed, 106 insertions(+), 90 deletions(-) diff --git a/benchmark/events/ee-emit.js b/benchmark/events/ee-emit.js index 87772222f0a467..e1d36e6863ace2 100644 --- a/benchmark/events/ee-emit.js +++ b/benchmark/events/ee-emit.js @@ -2,15 +2,17 @@ var common = require('../common.js'); var EventEmitter = require('events').EventEmitter; -var bench = common.createBenchmark(main, {n: [2e6]}); +var bench = common.createBenchmark(main, {n: [2e6], listeners: [10]}); function main(conf) { var n = conf.n | 0; + var listeners = conf.listeners | 0; var ee = new EventEmitter(); + ee.setMaxListeners(listeners + 1); - for (var k = 0; k < 10; k += 1) - ee.on('dummy', function() {}); + for (var k = 0; k < listeners; k += 1) + ee.on('dummy', function() { return 0; }); bench.start(); for (var i = 0; i < n; i += 1) { diff --git a/lib/events.js b/lib/events.js index 5ca9f2bb1a135a..5f9b2214919baf 100644 --- a/lib/events.js +++ b/lib/events.js @@ -96,65 +96,8 @@ EventEmitter.prototype.getMaxListeners = function getMaxListeners() { return $getMaxListeners(this); }; -// These standalone emit* functions are used to optimize calling of event -// handlers for fast cases because emit() itself often has a variable number of -// arguments and can be deoptimized because of that. These functions always have -// the same number of arguments and thus do not get deoptimized, so the code -// inside them can execute faster. -function emitNone(handler, isFn, self) { - if (isFn) - handler.call(self); - else { - var len = handler.length; - var listeners = arrayClone(handler, len); - for (var i = 0; i < len; ++i) - listeners[i].call(self); - } -} -function emitOne(handler, isFn, self, arg1) { - if (isFn) - handler.call(self, arg1); - else { - var len = handler.length; - var listeners = arrayClone(handler, len); - for (var i = 0; i < len; ++i) - listeners[i].call(self, arg1); - } -} -function emitTwo(handler, isFn, self, arg1, arg2) { - if (isFn) - handler.call(self, arg1, arg2); - else { - var len = handler.length; - var listeners = arrayClone(handler, len); - for (var i = 0; i < len; ++i) - listeners[i].call(self, arg1, arg2); - } -} -function emitThree(handler, isFn, self, arg1, arg2, arg3) { - if (isFn) - handler.call(self, arg1, arg2, arg3); - else { - var len = handler.length; - var listeners = arrayClone(handler, len); - for (var i = 0; i < len; ++i) - listeners[i].call(self, arg1, arg2, arg3); - } -} - -function emitMany(handler, isFn, self, args) { - if (isFn) - handler.apply(self, args); - else { - var len = handler.length; - var listeners = arrayClone(handler, len); - for (var i = 0; i < len; ++i) - listeners[i].apply(self, args); - } -} - EventEmitter.prototype.emit = function emit(type) { - var er, handler, len, args, i, events, domain; + var er, fn, i, events, domain; var needDomainExit = false; var doError = (type === 'error'); @@ -190,9 +133,9 @@ EventEmitter.prototype.emit = function emit(type) { return false; } - handler = events[type]; + fn = events[type]; - if (!handler) + if (!fn) return false; if (domain && this !== process) { @@ -200,28 +143,93 @@ EventEmitter.prototype.emit = function emit(type) { needDomainExit = true; } - var isFn = typeof handler === 'function'; - len = arguments.length; - switch (len) { - // fast cases - case 1: - emitNone(handler, isFn, this); - break; - case 2: - emitOne(handler, isFn, this, arguments[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]); - break; - // slower - default: - args = new Array(len - 1); - for (i = 1; i < len; i++) - args[i - 1] = arguments[i]; - emitMany(handler, isFn, this, args); + var len = arguments.length; + var args; + if (typeof fn === 'function') { + switch (len) { + case 1: fn.call(this); break; + case 2: fn.call(this, arguments[1]); break; + case 3: fn.call(this, arguments[1], arguments[2]); break; + case 4: fn.call(this, arguments[1], arguments[2], arguments[3]); + break; + default: + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + fn.apply(this, args); + } + } else { + var fnlen = fn.length; + fn = arrayClone(fn, fnlen); + switch (len) { + case 1: + fn[0].call(this); + fn[1].call(this); + if (fnlen === 2) break; + fn[2].call(this); + if (fnlen === 3) break; + fn[3].call(this); + if (fnlen === 4) break; + fn[4].call(this); + if (fnlen === 5) break; + for (i = 5; i < fnlen; ++i) + fn[i].call(this); + break; + case 2: + fn[0].call(this, arguments[1]); + fn[1].call(this, arguments[1]); + if (fnlen === 2) break; + fn[2].call(this, arguments[1]); + if (fnlen === 3) break; + fn[3].call(this, arguments[1]); + if (fnlen === 4) break; + fn[4].call(this, arguments[1]); + if (fnlen === 5) break; + for (i = 5; i < fnlen; ++i) + fn[i].call(this, arguments[1]); + break; + case 3: + fn[0].call(this, arguments[1], arguments[2]); + fn[1].call(this, arguments[1], arguments[2]); + if (fnlen === 2) break; + fn[2].call(this, arguments[1], arguments[2]); + if (fnlen === 3) break; + fn[3].call(this, arguments[1], arguments[2]); + if (fnlen === 4) break; + fn[4].call(this, arguments[1], arguments[2]); + if (fnlen === 5) break; + for (i = 5; i < fnlen; ++i) + fn[i].call(this, arguments[1], arguments[2]); + break; + case 4: + fn[0].call(this, arguments[1], arguments[2], arguments[3]); + fn[1].call(this, arguments[1], arguments[2], arguments[3]); + if (fnlen === 2) break; + fn[2].call(this, arguments[1], arguments[2], arguments[3]); + if (fnlen === 3) break; + fn[3].call(this, arguments[1], arguments[2], arguments[3]); + if (fnlen === 4) break; + fn[4].call(this, arguments[1], arguments[2], arguments[3]); + if (fnlen === 5) break; + for (i = 5; i < fnlen; ++i) + fn[i].call(this, arguments[1], arguments[2], arguments[3]); + break; + default: + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + fn[0].apply(this, args); + fn[1].apply(this, args); + if (fnlen === 2) break; + fn[2].apply(this, args); + if (fnlen === 3) break; + fn[3].apply(this, args); + if (fnlen === 4) break; + fn[4].apply(this, args); + if (fnlen === 5) break; + for (i = 5; i < fnlen; ++i) + fn[i].apply(this, args); + } } if (needDomainExit) @@ -497,7 +505,13 @@ function spliceOne(list, index) { } function arrayClone(arr, n) { - var copy = new Array(n); + switch (n) { + case 2: return [arr[0], arr[1]]; + case 3: return [arr[0], arr[1], arr[2]]; + case 4: return [arr[0], arr[1], arr[2], arr[3]]; + case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]]; + } + const copy = new Array(n); for (var i = 0; i < n; ++i) copy[i] = arr[i]; return copy; diff --git a/test/message/stdin_messages.out b/test/message/stdin_messages.out index ad1688f15d09b8..3145d50894771b 100644 --- a/test/message/stdin_messages.out +++ b/test/message/stdin_messages.out @@ -9,10 +9,10 @@ SyntaxError: Strict mode code may not include a with statement at Module._compile (module.js:*:*) at evalScript (bootstrap_node.js:*:*) at Socket. (bootstrap_node.js:*:*) - at emitNone (events.js:*:*) at Socket.emit (events.js:*:*) at endReadableNT (_stream_readable.js:*:*) at _combinedTickCallback (internal/process/next_tick.js:*:*) + at process._tickCallback (internal/process/next_tick.js:*:*) 42 42 [stdin]:1 @@ -27,9 +27,9 @@ Error: hello at Module._compile (module.js:*:*) at evalScript (bootstrap_node.js:*:*) at Socket. (bootstrap_node.js:*:*) - at emitNone (events.js:*:*) at Socket.emit (events.js:*:*) at endReadableNT (_stream_readable.js:*:*) + at _combinedTickCallback (internal/process/next_tick.js:*:*) [stdin]:1 throw new Error("hello") ^ @@ -42,9 +42,9 @@ Error: hello at Module._compile (module.js:*:*) at evalScript (bootstrap_node.js:*:*) at Socket. (bootstrap_node.js:*:*) - at emitNone (events.js:*:*) at Socket.emit (events.js:*:*) at endReadableNT (_stream_readable.js:*:*) + at _combinedTickCallback (internal/process/next_tick.js:*:*) 100 [stdin]:1 var x = 100; y = x; @@ -58,9 +58,9 @@ ReferenceError: y is not defined at Module._compile (module.js:*:*) at evalScript (bootstrap_node.js:*:*) at Socket. (bootstrap_node.js:*:*) - at emitNone (events.js:*:*) at Socket.emit (events.js:*:*) at endReadableNT (_stream_readable.js:*:*) + at _combinedTickCallback (internal/process/next_tick.js:*:*) [stdin]:1 var ______________________________________________; throw 10