From f44f18a857a802367de1e38b7017bae7ab0dc44b Mon Sep 17 00:00:00 2001 From: Anatoli Papirovski Date: Tue, 7 Nov 2017 13:22:49 -0500 Subject: [PATCH] events: remove emit micro-optimizations With improvements in V8, using separate emit functions is no longer necessary and can instead be replaced by the spread operator. improvement confidence p.value events/ee-emit.js n=2000000 2.98 % 0.09852489 events/ee-emit-2-args.js n=2000000 4.19 % *** 0.0001914216 events/ee-emit-6-args.js n=2000000 61.69 % *** 6.611964e-35 events/ee-emit-diff-args.js n=2000000 -0.36 % 0.305069 events/ee-once.js n=20000000 6.42 % *** 1.27831e-06 PR-URL: https://github.com/nodejs/node/pull/16869 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Refael Ackermann Reviewed-By: Evan Lucas Reviewed-By: Bryan English Reviewed-By: Luigi Pinca Reviewed-By: Timothy Gu Reviewed-By: Franziska Hinkelmann Reviewed-By: Benedikt Meurer Reviewed-By: James M Snell Reviewed-By: Brian White --- benchmark/events/ee-emit-multi-args.js | 20 ------ benchmark/events/ee-emit.js | 44 ++++++++++-- lib/events.js | 96 +++----------------------- test/message/stdin_messages.out | 8 +-- 4 files changed, 51 insertions(+), 117 deletions(-) delete mode 100644 benchmark/events/ee-emit-multi-args.js diff --git a/benchmark/events/ee-emit-multi-args.js b/benchmark/events/ee-emit-multi-args.js deleted file mode 100644 index ffe6c16a402f07..00000000000000 --- a/benchmark/events/ee-emit-multi-args.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; -const common = require('../common.js'); -const EventEmitter = require('events').EventEmitter; - -const bench = common.createBenchmark(main, { n: [2e6] }); - -function main(conf) { - const n = conf.n | 0; - - const ee = new EventEmitter(); - - for (var k = 0; k < 10; k += 1) - ee.on('dummy', function() {}); - - bench.start(); - for (var i = 0; i < n; i += 1) { - ee.emit('dummy', 5, true); - } - bench.end(n); -} diff --git a/benchmark/events/ee-emit.js b/benchmark/events/ee-emit.js index 9eef9f675605e2..3d7eb43b228b71 100644 --- a/benchmark/events/ee-emit.js +++ b/benchmark/events/ee-emit.js @@ -2,19 +2,51 @@ const common = require('../common.js'); const EventEmitter = require('events').EventEmitter; -const bench = common.createBenchmark(main, { n: [2e6] }); +const bench = common.createBenchmark(main, { + n: [2e6], + argc: [0, 2, 4, 10], + listeners: [1, 5, 10], +}); function main(conf) { const n = conf.n | 0; + const argc = conf.argc | 0; + const listeners = Math.max(conf.listeners | 0, 1); const ee = new EventEmitter(); - for (var k = 0; k < 10; k += 1) + for (var k = 0; k < listeners; k += 1) ee.on('dummy', function() {}); - bench.start(); - for (var i = 0; i < n; i += 1) { - ee.emit('dummy'); + var i; + switch (argc) { + case 2: + bench.start(); + for (i = 0; i < n; i += 1) { + ee.emit('dummy', true, 5); + } + bench.end(n); + break; + case 4: + bench.start(); + for (i = 0; i < n; i += 1) { + ee.emit('dummy', true, 5, 10, false); + } + bench.end(n); + break; + case 10: + bench.start(); + for (i = 0; i < n; i += 1) { + ee.emit('dummy', true, 5, 10, false, 5, 'string', true, false, 11, 20); + } + bench.end(n); + break; + default: + bench.start(); + for (i = 0; i < n; i += 1) { + ee.emit('dummy'); + } + bench.end(n); + break; } - bench.end(n); } diff --git a/lib/events.js b/lib/events.js index c99314f0dff802..2a83ab7dc64c7b 100644 --- a/lib/events.js +++ b/lib/events.js @@ -105,63 +105,6 @@ 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, ...args) { let doError = (type === 'error'); @@ -212,22 +155,13 @@ EventEmitter.prototype.emit = function emit(type, ...args) { needDomainExit = true; } - const isFn = typeof handler === 'function'; - switch (args.length) { - case 0: - emitNone(handler, isFn, this); - break; - case 1: - emitOne(handler, isFn, this, args[0]); - break; - case 2: - emitTwo(handler, isFn, this, args[0], args[1]); - break; - case 3: - emitThree(handler, isFn, this, args[0], args[1], args[2]); - break; - default: - emitMany(handler, isFn, this, args); + if (typeof handler === 'function') { + handler.apply(this, args); + } else { + const len = handler.length; + const listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].apply(this, args); } if (needDomainExit) @@ -313,23 +247,11 @@ EventEmitter.prototype.prependListener = return _addListener(this, type, listener, true); }; -function onceWrapper() { +function onceWrapper(...args) { if (!this.fired) { this.target.removeListener(this.type, this.wrapFn); this.fired = true; - switch (arguments.length) { - case 0: - return this.listener.call(this.target); - case 1: - return this.listener.call(this.target, arguments[0]); - case 2: - return this.listener.call(this.target, arguments[0], arguments[1]); - case 3: - return this.listener.call(this.target, arguments[0], arguments[1], - arguments[2]); - default: - this.listener.apply(this.target, arguments); - } + this.listener.apply(this.target, args); } } 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