Skip to content

Commit 9c7d3b0

Browse files
committed
events: improve emit() performance
1 parent d3e3ade commit 9c7d3b0

File tree

3 files changed

+106
-90
lines changed

3 files changed

+106
-90
lines changed

benchmark/events/ee-emit.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
var common = require('../common.js');
33
var EventEmitter = require('events').EventEmitter;
44

5-
var bench = common.createBenchmark(main, {n: [2e6]});
5+
var bench = common.createBenchmark(main, {n: [2e6], listeners: [10]});
66

77
function main(conf) {
88
var n = conf.n | 0;
9+
var listeners = conf.listeners | 0;
910

1011
var ee = new EventEmitter();
12+
ee.setMaxListeners(listeners + 1);
1113

12-
for (var k = 0; k < 10; k += 1)
13-
ee.on('dummy', function() {});
14+
for (var k = 0; k < listeners; k += 1)
15+
ee.on('dummy', function() { return 0; });
1416

1517
bench.start();
1618
for (var i = 0; i < n; i += 1) {

lib/events.js

+97-83
Original file line numberDiff line numberDiff line change
@@ -96,65 +96,8 @@ EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
9696
return $getMaxListeners(this);
9797
};
9898

99-
// These standalone emit* functions are used to optimize calling of event
100-
// handlers for fast cases because emit() itself often has a variable number of
101-
// arguments and can be deoptimized because of that. These functions always have
102-
// the same number of arguments and thus do not get deoptimized, so the code
103-
// inside them can execute faster.
104-
function emitNone(handler, isFn, self) {
105-
if (isFn)
106-
handler.call(self);
107-
else {
108-
var len = handler.length;
109-
var listeners = arrayClone(handler, len);
110-
for (var i = 0; i < len; ++i)
111-
listeners[i].call(self);
112-
}
113-
}
114-
function emitOne(handler, isFn, self, arg1) {
115-
if (isFn)
116-
handler.call(self, arg1);
117-
else {
118-
var len = handler.length;
119-
var listeners = arrayClone(handler, len);
120-
for (var i = 0; i < len; ++i)
121-
listeners[i].call(self, arg1);
122-
}
123-
}
124-
function emitTwo(handler, isFn, self, arg1, arg2) {
125-
if (isFn)
126-
handler.call(self, arg1, arg2);
127-
else {
128-
var len = handler.length;
129-
var listeners = arrayClone(handler, len);
130-
for (var i = 0; i < len; ++i)
131-
listeners[i].call(self, arg1, arg2);
132-
}
133-
}
134-
function emitThree(handler, isFn, self, arg1, arg2, arg3) {
135-
if (isFn)
136-
handler.call(self, arg1, arg2, arg3);
137-
else {
138-
var len = handler.length;
139-
var listeners = arrayClone(handler, len);
140-
for (var i = 0; i < len; ++i)
141-
listeners[i].call(self, arg1, arg2, arg3);
142-
}
143-
}
144-
145-
function emitMany(handler, isFn, self, args) {
146-
if (isFn)
147-
handler.apply(self, args);
148-
else {
149-
var len = handler.length;
150-
var listeners = arrayClone(handler, len);
151-
for (var i = 0; i < len; ++i)
152-
listeners[i].apply(self, args);
153-
}
154-
}
155-
15699
EventEmitter.prototype.emit = function emit(type) {
157-
var er, handler, len, args, i, events, domain;
100+
var er, fn, i, events, domain;
158101
var needDomainExit = false;
159102
var doError = (type === 'error');
160103

@@ -190,38 +133,103 @@ EventEmitter.prototype.emit = function emit(type) {
190133
return false;
191134
}
192135

193-
handler = events[type];
136+
fn = events[type];
194137

195-
if (!handler)
138+
if (!fn)
196139
return false;
197140

198141
if (domain && this !== process) {
199142
domain.enter();
200143
needDomainExit = true;
201144
}
202145

203-
var isFn = typeof handler === 'function';
204-
len = arguments.length;
205-
switch (len) {
206-
// fast cases
207-
case 1:
208-
emitNone(handler, isFn, this);
209-
break;
210-
case 2:
211-
emitOne(handler, isFn, this, arguments[1]);
212-
break;
213-
case 3:
214-
emitTwo(handler, isFn, this, arguments[1], arguments[2]);
215-
break;
216-
case 4:
217-
emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
218-
break;
219-
// slower
220-
default:
221-
args = new Array(len - 1);
222-
for (i = 1; i < len; i++)
223-
args[i - 1] = arguments[i];
224-
emitMany(handler, isFn, this, args);
146+
var len = arguments.length;
147+
var args;
148+
if (typeof fn === 'function') {
149+
switch (len) {
150+
case 1: fn.call(this); break;
151+
case 2: fn.call(this, arguments[1]); break;
152+
case 3: fn.call(this, arguments[1], arguments[2]); break;
153+
case 4: fn.call(this, arguments[1], arguments[2], arguments[3]);
154+
break;
155+
default:
156+
args = new Array(len - 1);
157+
for (i = 1; i < len; i++)
158+
args[i - 1] = arguments[i];
159+
fn.apply(this, args);
160+
}
161+
} else {
162+
var fnlen = fn.length;
163+
fn = arrayClone(fn, fnlen);
164+
switch (len) {
165+
case 1:
166+
fn[0].call(this);
167+
fn[1].call(this);
168+
if (fnlen === 2) break;
169+
fn[2].call(this);
170+
if (fnlen === 3) break;
171+
fn[3].call(this);
172+
if (fnlen === 4) break;
173+
fn[4].call(this);
174+
if (fnlen === 5) break;
175+
for (i = 5; i < fnlen; ++i)
176+
fn[i].call(this);
177+
break;
178+
case 2:
179+
fn[0].call(this, arguments[1]);
180+
fn[1].call(this, arguments[1]);
181+
if (fnlen === 2) break;
182+
fn[2].call(this, arguments[1]);
183+
if (fnlen === 3) break;
184+
fn[3].call(this, arguments[1]);
185+
if (fnlen === 4) break;
186+
fn[4].call(this, arguments[1]);
187+
if (fnlen === 5) break;
188+
for (i = 5; i < fnlen; ++i)
189+
fn[i].call(this, arguments[1]);
190+
break;
191+
case 3:
192+
fn[0].call(this, arguments[1], arguments[2]);
193+
fn[1].call(this, arguments[1], arguments[2]);
194+
if (fnlen === 2) break;
195+
fn[2].call(this, arguments[1], arguments[2]);
196+
if (fnlen === 3) break;
197+
fn[3].call(this, arguments[1], arguments[2]);
198+
if (fnlen === 4) break;
199+
fn[4].call(this, arguments[1], arguments[2]);
200+
if (fnlen === 5) break;
201+
for (i = 5; i < fnlen; ++i)
202+
fn[i].call(this, arguments[1], arguments[2]);
203+
break;
204+
case 4:
205+
fn[0].call(this, arguments[1], arguments[2], arguments[3]);
206+
fn[1].call(this, arguments[1], arguments[2], arguments[3]);
207+
if (fnlen === 2) break;
208+
fn[2].call(this, arguments[1], arguments[2], arguments[3]);
209+
if (fnlen === 3) break;
210+
fn[3].call(this, arguments[1], arguments[2], arguments[3]);
211+
if (fnlen === 4) break;
212+
fn[4].call(this, arguments[1], arguments[2], arguments[3]);
213+
if (fnlen === 5) break;
214+
for (i = 5; i < fnlen; ++i)
215+
fn[i].call(this, arguments[1], arguments[2], arguments[3]);
216+
break;
217+
default:
218+
args = new Array(len - 1);
219+
for (i = 1; i < len; i++)
220+
args[i - 1] = arguments[i];
221+
fn[0].apply(this, args);
222+
fn[1].apply(this, args);
223+
if (fnlen === 2) break;
224+
fn[2].apply(this, args);
225+
if (fnlen === 3) break;
226+
fn[3].apply(this, args);
227+
if (fnlen === 4) break;
228+
fn[4].apply(this, args);
229+
if (fnlen === 5) break;
230+
for (i = 5; i < fnlen; ++i)
231+
fn[i].apply(this, args);
232+
}
225233
}
226234

227235
if (needDomainExit)
@@ -497,7 +505,13 @@ function spliceOne(list, index) {
497505
}
498506

499507
function arrayClone(arr, n) {
500-
var copy = new Array(n);
508+
switch (n) {
509+
case 2: return [arr[0], arr[1]];
510+
case 3: return [arr[0], arr[1], arr[2]];
511+
case 4: return [arr[0], arr[1], arr[2], arr[3]];
512+
case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]];
513+
}
514+
const copy = new Array(n);
501515
for (var i = 0; i < n; ++i)
502516
copy[i] = arr[i];
503517
return copy;

test/message/stdin_messages.out

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ SyntaxError: Strict mode code may not include a with statement
99
at Module._compile (module.js:*:*)
1010
at evalScript (bootstrap_node.js:*:*)
1111
at Socket.<anonymous> (bootstrap_node.js:*:*)
12-
at emitNone (events.js:*:*)
1312
at Socket.emit (events.js:*:*)
1413
at endReadableNT (_stream_readable.js:*:*)
1514
at _combinedTickCallback (internal/process/next_tick.js:*:*)
15+
at process._tickCallback (internal/process/next_tick.js:*:*)
1616
42
1717
42
1818
[stdin]:1
@@ -27,9 +27,9 @@ Error: hello
2727
at Module._compile (module.js:*:*)
2828
at evalScript (bootstrap_node.js:*:*)
2929
at Socket.<anonymous> (bootstrap_node.js:*:*)
30-
at emitNone (events.js:*:*)
3130
at Socket.emit (events.js:*:*)
3231
at endReadableNT (_stream_readable.js:*:*)
32+
at _combinedTickCallback (internal/process/next_tick.js:*:*)
3333
[stdin]:1
3434
throw new Error("hello")
3535
^
@@ -42,9 +42,9 @@ Error: hello
4242
at Module._compile (module.js:*:*)
4343
at evalScript (bootstrap_node.js:*:*)
4444
at Socket.<anonymous> (bootstrap_node.js:*:*)
45-
at emitNone (events.js:*:*)
4645
at Socket.emit (events.js:*:*)
4746
at endReadableNT (_stream_readable.js:*:*)
47+
at _combinedTickCallback (internal/process/next_tick.js:*:*)
4848
100
4949
[stdin]:1
5050
var x = 100; y = x;
@@ -58,9 +58,9 @@ ReferenceError: y is not defined
5858
at Module._compile (module.js:*:*)
5959
at evalScript (bootstrap_node.js:*:*)
6060
at Socket.<anonymous> (bootstrap_node.js:*:*)
61-
at emitNone (events.js:*:*)
6261
at Socket.emit (events.js:*:*)
6362
at endReadableNT (_stream_readable.js:*:*)
63+
at _combinedTickCallback (internal/process/next_tick.js:*:*)
6464

6565
[stdin]:1
6666
var ______________________________________________; throw 10

0 commit comments

Comments
 (0)