Skip to content

Commit eb8f7ee

Browse files
aduh95targos
authored andcommitted
lib: revert primordials in a hot path
Evidence has shown that use of primordials have sometimes an impact of performance. This commit reverts the changes who are most likely to be responsible for performance regression in the HTTP response path. PR-URL: #38248 Backport-PR-URL: #38972 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 07c55d2 commit eb8f7ee

File tree

9 files changed

+41
-53
lines changed

9 files changed

+41
-53
lines changed

lib/_http_outgoing.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,8 @@ function _storeHeader(firstLine, headers) {
380380
}
381381
} else if (ArrayIsArray(headers)) {
382382
if (headers.length && ArrayIsArray(headers[0])) {
383-
for (const entry of headers) {
383+
for (let i = 0; i < headers.length; i++) {
384+
const entry = headers[i];
384385
processHeader(this, state, entry[0], entry[1], true);
385386
}
386387
} else {

lib/_http_server.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,9 @@ Server.prototype[EE.captureRejectionSymbol] = function(err, event, ...args) {
398398
const [ , res] = args;
399399
if (!res.headersSent && !res.writableEnded) {
400400
// Don't leak headers.
401-
for (const name of res.getHeaderNames()) {
402-
res.removeHeader(name);
401+
const names = res.getHeaderNames();
402+
for (let i = 0; i < names.length; i++) {
403+
res.removeHeader(names[i]);
403404
}
404405
res.statusCode = 500;
405406
res.end(STATUS_CODES[500]);
@@ -409,7 +410,7 @@ Server.prototype[EE.captureRejectionSymbol] = function(err, event, ...args) {
409410
break;
410411
default:
411412
net.Server.prototype[SymbolFor('nodejs.rejection')]
412-
.call(this, err, event, ...args);
413+
.apply(this, arguments);
413414
}
414415
};
415416

lib/events.js

+10-14
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,10 @@
2222
'use strict';
2323

2424
const {
25-
ArrayPrototypeForEach,
26-
ArrayPrototypePush,
2725
ArrayPrototypeSlice,
2826
Boolean,
2927
Error,
3028
ErrorCaptureStackTrace,
31-
FunctionPrototypeCall,
3229
MathMin,
3330
NumberIsNaN,
3431
ObjectCreate,
@@ -39,7 +36,6 @@ const {
3936
Promise,
4037
PromiseReject,
4138
PromiseResolve,
42-
ReflectApply,
4339
ReflectOwnKeys,
4440
String,
4541
Symbol,
@@ -84,7 +80,7 @@ const lazyDOMException = hideStackFrames((message, name) => {
8480

8581

8682
function EventEmitter(opts) {
87-
FunctionPrototypeCall(EventEmitter.init, this, opts);
83+
EventEmitter.init.call(this, opts);
8884
}
8985
module.exports = EventEmitter;
9086
module.exports.once = once;
@@ -175,8 +171,8 @@ EventEmitter.setMaxListeners =
175171
if (isEventTarget === undefined)
176172
isEventTarget = require('internal/event_target').isEventTarget;
177173

178-
// Performance for forEach is now comparable with regular for-loop
179-
ArrayPrototypeForEach(eventTargets, (target) => {
174+
for (let i = 0; i < eventTargets.length; i++) {
175+
const target = eventTargets[i];
180176
if (isEventTarget(target)) {
181177
target[kMaxEventTargetListeners] = n;
182178
target[kMaxEventTargetListenersWarned] = false;
@@ -188,7 +184,7 @@ EventEmitter.setMaxListeners =
188184
['EventEmitter', 'EventTarget'],
189185
target);
190186
}
191-
});
187+
}
192188
}
193189
};
194190

@@ -227,7 +223,7 @@ function addCatch(that, promise, type, args) {
227223
const then = promise.then;
228224

229225
if (typeof then === 'function') {
230-
FunctionPrototypeCall(then, promise, undefined, function(err) {
226+
then.call(promise, undefined, function(err) {
231227
// The callback is called with nextTick to avoid a follow-up
232228
// rejection from this promise.
233229
process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
@@ -376,7 +372,7 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
376372
return false;
377373

378374
if (typeof handler === 'function') {
379-
const result = ReflectApply(handler, this, args);
375+
const result = handler.apply(this, args);
380376

381377
// We check if result is undefined first because that
382378
// is the most common case so we do not pay any perf
@@ -388,7 +384,7 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
388384
const len = handler.length;
389385
const listeners = arrayClone(handler);
390386
for (let i = 0; i < len; ++i) {
391-
const result = ReflectApply(listeners[i], this, args);
387+
const result = listeners[i].apply(this, args);
392388

393389
// We check if result is undefined first because that
394390
// is the most common case so we do not pay any perf
@@ -698,7 +694,7 @@ function getEventListeners(emitterOrTarget, type) {
698694
const listeners = [];
699695
let handler = root?.next;
700696
while (handler?.listener !== undefined) {
701-
ArrayPrototypePush(listeners, handler.listener);
697+
listeners.push(handler.listener);
702698
handler = handler.next;
703699
}
704700
return listeners;
@@ -842,7 +838,7 @@ function on(emitter, event, options) {
842838

843839
// Wait until an event happens
844840
return new Promise(function(resolve, reject) {
845-
ArrayPrototypePush(unconsumedPromises, { resolve, reject });
841+
unconsumedPromises.push({ resolve, reject });
846842
});
847843
},
848844

@@ -906,7 +902,7 @@ function on(emitter, event, options) {
906902
if (promise) {
907903
promise.resolve(createIterResult(args, false));
908904
} else {
909-
ArrayPrototypePush(unconsumedEvents, args);
905+
unconsumedEvents.push(args);
910906
}
911907
}
912908

lib/internal/async_hooks.js

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
'use strict';
22

33
const {
4-
ArrayPrototypePop,
54
ArrayPrototypeSlice,
6-
ArrayPrototypeUnshift,
75
ErrorCaptureStackTrace,
8-
FunctionPrototypeBind,
96
ObjectPrototypeHasOwnProperty,
107
ObjectDefineProperty,
118
Promise,
12-
ReflectApply,
139
Symbol,
1410
} = primordials;
1511

@@ -129,16 +125,16 @@ function callbackTrampoline(asyncId, resource, cb, ...args) {
129125

130126
let result;
131127
if (asyncId === 0 && typeof domain_cb === 'function') {
132-
ArrayPrototypeUnshift(args, cb);
133-
result = ReflectApply(domain_cb, this, args);
128+
args.unshift(cb);
129+
result = domain_cb.apply(this, args);
134130
} else {
135-
result = ReflectApply(cb, this, args);
131+
result = cb.apply(this, args);
136132
}
137133

138134
if (asyncId !== 0 && hasHooks(kAfter))
139135
emitAfterNative(asyncId);
140136

141-
ArrayPrototypePop(execution_async_resources);
137+
execution_async_resources.pop();
142138
return result;
143139
}
144140

@@ -256,7 +252,7 @@ function emitHook(symbol, asyncId) {
256252
}
257253

258254
function emitHookFactory(symbol, name) {
259-
const fn = FunctionPrototypeBind(emitHook, undefined, symbol);
255+
const fn = emitHook.bind(undefined, symbol);
260256

261257
// Set the name property of the function as it looks good in the stack trace.
262258
ObjectDefineProperty(fn, 'name', {
@@ -429,14 +425,14 @@ function clearDefaultTriggerAsyncId() {
429425

430426
function defaultTriggerAsyncIdScope(triggerAsyncId, block, ...args) {
431427
if (triggerAsyncId === undefined)
432-
return ReflectApply(block, null, args);
428+
return block.apply(null, args);
433429
// CHECK(NumberIsSafeInteger(triggerAsyncId))
434430
// CHECK(triggerAsyncId > 0)
435431
const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
436432
async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId;
437433

438434
try {
439-
return ReflectApply(block, null, args);
435+
return block.apply(null, args);
440436
} finally {
441437
async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId;
442438
}
@@ -533,7 +529,7 @@ function popAsyncContext(asyncId) {
533529
const offset = stackLength - 1;
534530
async_id_fields[kExecutionAsyncId] = async_wrap.async_ids_stack[2 * offset];
535531
async_id_fields[kTriggerAsyncId] = async_wrap.async_ids_stack[2 * offset + 1];
536-
ArrayPrototypePop(execution_async_resources);
532+
execution_async_resources.pop();
537533
async_hook_fields[kStackLength] = offset;
538534
return offset > 0;
539535
}

lib/internal/per_context/primordials.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
// so that Node.js's builtin modules do not need to later look these up from
77
// the global proxy, which can be mutated by users.
88

9-
// TODO(joyeecheung): we can restrict access to these globals in builtin
10-
// modules through the JS linter, for example: ban access such as `Object`
11-
// (which falls back to a lookup in the global proxy) in favor of
12-
// `primordials.Object` where `primordials` is a lexical variable passed
13-
// by the native module compiler.
9+
// Use of primordials have sometimes a dramatic impact on performance, please
10+
// benchmark all changes made in performance-sensitive areas of the codebase.
11+
// See: https://github.com./nodejs/node/pull/38248
1412

1513
// `uncurryThis` is equivalent to `func => Function.prototype.call.bind(func)`.
1614
// It is using `bind.bind(call)` to avoid using `Function.prototype.bind`

lib/internal/streams/readable.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,8 @@ Readable.prototype.unpipe = function(dest) {
823823
state.pipes = [];
824824
this.pause();
825825

826-
for (const dest of dests)
827-
dest.emit('unpipe', this, { hasUnpiped: false });
826+
for (let i = 0; i < dests.length; i++)
827+
dests[i].emit('unpipe', this, { hasUnpiped: false });
828828
return this;
829829
}
830830

lib/internal/timers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ const {
7878
NumberIsFinite,
7979
NumberMIN_SAFE_INTEGER,
8080
ObjectCreate,
81-
Symbol,
8281
ReflectApply,
82+
Symbol,
8383
} = primordials;
8484

8585
const {

lib/net.js

+10-14
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,13 @@
2424
const {
2525
ArrayIsArray,
2626
ArrayPrototypeIndexOf,
27-
ArrayPrototypePush,
28-
ArrayPrototypeSplice,
2927
Boolean,
3028
Error,
31-
FunctionPrototype,
32-
FunctionPrototypeCall,
3329
Number,
3430
NumberIsNaN,
3531
NumberParseInt,
3632
ObjectDefineProperty,
3733
ObjectSetPrototypeOf,
38-
ReflectApply,
3934
Symbol,
4035
} = primordials;
4136

@@ -129,7 +124,7 @@ const DEFAULT_IPV6_ADDR = '::';
129124

130125
const isWindows = process.platform === 'win32';
131126

132-
const noop = FunctionPrototype;
127+
const noop = () => {};
133128

134129
function getFlags(ipv6Only) {
135130
return ipv6Only === true ? TCPConstants.UV_TCP_IPV6ONLY : 0;
@@ -304,7 +299,7 @@ function Socket(options) {
304299
options.autoDestroy = false;
305300
// Handle strings directly.
306301
options.decodeStrings = false;
307-
ReflectApply(stream.Duplex, this, [options]);
302+
stream.Duplex.call(this, options);
308303

309304
// Default to *not* allowing half open sockets.
310305
this.allowHalfOpen = Boolean(allowHalfOpen);
@@ -594,7 +589,8 @@ Socket.prototype._read = function(n) {
594589

595590

596591
Socket.prototype.end = function(data, encoding, callback) {
597-
ReflectApply(stream.Duplex.prototype.end, this, [data, encoding, callback]);
592+
stream.Duplex.prototype.end.call(this,
593+
data, encoding, callback);
598594
DTRACE_NET_STREAM_END(this);
599595
return this;
600596
};
@@ -610,7 +606,7 @@ Socket.prototype.pause = function() {
610606
this.destroy(errnoException(err, 'read'));
611607
}
612608
}
613-
return FunctionPrototypeCall(stream.Duplex.prototype.pause, this);
609+
return stream.Duplex.prototype.pause.call(this);
614610
};
615611

616612

@@ -619,7 +615,7 @@ Socket.prototype.resume = function() {
619615
!this._handle.reading) {
620616
tryReadStart(this);
621617
}
622-
return FunctionPrototypeCall(stream.Duplex.prototype.resume, this);
618+
return stream.Duplex.prototype.resume.call(this);
623619
};
624620

625621

@@ -628,7 +624,7 @@ Socket.prototype.read = function(n) {
628624
!this._handle.reading) {
629625
tryReadStart(this);
630626
}
631-
return ReflectApply(stream.Duplex.prototype.read, this, [n]);
627+
return stream.Duplex.prototype.read.call(this, n);
632628
};
633629

634630

@@ -1167,7 +1163,7 @@ function Server(options, connectionListener) {
11671163
if (!(this instanceof Server))
11681164
return new Server(options, connectionListener);
11691165

1170-
FunctionPrototypeCall(EventEmitter, this);
1166+
EventEmitter.call(this);
11711167

11721168
if (typeof options === 'function') {
11731169
connectionListener = options;
@@ -1693,10 +1689,10 @@ ObjectDefineProperty(Socket.prototype, '_handle', {
16931689

16941690
Server.prototype._setupWorker = function(socketList) {
16951691
this._usingWorkers = true;
1696-
ArrayPrototypePush(this._workers, socketList);
1692+
this._workers.push(socketList);
16971693
socketList.once('exit', (socketList) => {
16981694
const index = ArrayPrototypeIndexOf(this._workers, socketList);
1699-
ArrayPrototypeSplice(this._workers, index, 1);
1695+
this._workers.splice(index, 1);
17001696
});
17011697
};
17021698

test/parallel/test-stream-pipe-await-drain.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ writer1.once('chunk-received', () => {
2727
reader._readableState.awaitDrainWriters.size,
2828
0,
2929
'awaitDrain initial value should be 0, actual is ' +
30-
reader._readableState.awaitDrainWriters
30+
reader._readableState.awaitDrainWriters.size
3131
);
3232
setImmediate(() => {
3333
// This one should *not* get through to writer1 because writer2 is not

0 commit comments

Comments
 (0)