Skip to content

Commit

Permalink
events: improve emit() performance
Browse files Browse the repository at this point in the history
  • Loading branch information
mscdex committed May 23, 2017
1 parent d3e3ade commit 9c7d3b0
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 90 deletions.
8 changes: 5 additions & 3 deletions benchmark/events/ee-emit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
180 changes: 97 additions & 83 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -190,38 +133,103 @@ EventEmitter.prototype.emit = function emit(type) {
return false;
}

handler = events[type];
fn = events[type];

if (!handler)
if (!fn)
return false;

if (domain && this !== process) {
domain.enter();
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)
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions test/message/stdin_messages.out
Original file line number Diff line number Diff line change
Expand Up @@ -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.<anonymous> (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
Expand All @@ -27,9 +27,9 @@ Error: hello
at Module._compile (module.js:*:*)
at evalScript (bootstrap_node.js:*:*)
at Socket.<anonymous> (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")
^
Expand All @@ -42,9 +42,9 @@ Error: hello
at Module._compile (module.js:*:*)
at evalScript (bootstrap_node.js:*:*)
at Socket.<anonymous> (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;
Expand All @@ -58,9 +58,9 @@ ReferenceError: y is not defined
at Module._compile (module.js:*:*)
at evalScript (bootstrap_node.js:*:*)
at Socket.<anonymous> (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
Expand Down

0 comments on commit 9c7d3b0

Please sign in to comment.