Skip to content

Commit 24cb5f2

Browse files
committed
http: keep request option copies in fast mode
The options of every client request are copied several times: once in ClientRequest to merge the URL and the options, once more to build the options handed to the agent, and again in Agent#addRequest (on every request, including keep-alive reuse) and createSocket to merge in the agent options. Each copy was a `{ __proto__: null, ...}` literal or an Object.assign() onto `{ __proto__: null }`. V8 creates such an object in dictionary mode, and copying from a dictionary-mode source takes the generic slow path, so every copy in the chain was slow and fed a slow source into the next one. Build each copy as an ordinary object literal and set its prototype to null afterwards, which leaves a fast-mode object with a null prototype and the same own properties in the same order. The same is done for the copy of the options in the http and https Agent constructors, which is the source of the agent options merged into every request. On a keep-alive client against a raw TCP responder (10 runs each, medians, busy machine) this goes from 68.7k to 83.1k requests per second and from 8.7 to 6.4 us of user CPU per request. Assisted-by: claude:opus-5.5 Signed-off-by: Gürgün Dayıoğlu <hey@gurgun.day>
1 parent 2aef805 commit 24cb5f2

3 files changed

Lines changed: 7 additions & 7 deletions

File tree

‎lib/https.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ function Agent(options) {
489489
if (!(this instanceof Agent))
490490
return new Agent(options);
491491

492-
options = { __proto__: null, ...options };
492+
options = ObjectSetPrototypeOf({ ...options }, null);
493493
validateOpenSSLEngineOptions(options);
494494
options.defaultPort ??= 443;
495495
options.protocol ??= 'https:';

‎lib/internal/http/agent.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ function Agent(options) {
158158

159159
EventEmitter.call(this);
160160

161-
this.options = { __proto__: null, ...options };
161+
this.options = ObjectSetPrototypeOf({ ...options }, null);
162162

163163
this.defaultPort = this.options.defaultPort || 80;
164164
this.protocol = this.options.protocol || 'http:';
@@ -375,7 +375,7 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
375375
}
376376

377377
// Here the agent options will override per-request options.
378-
options = { __proto__: null, ...options, ...this.options };
378+
options = ObjectSetPrototypeOf({ ...options, ...this.options }, null);
379379
if (options.socketPath)
380380
options.path = options.socketPath;
381381

@@ -445,7 +445,7 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
445445

446446
Agent.prototype.createSocket = function createSocket(req, options, cb) {
447447
// Here the agent options will override per-request options.
448-
options = { __proto__: null, ...options, ...this.options };
448+
options = ObjectSetPrototypeOf({ ...options, ...this.options }, null);
449449
if (options.socketPath)
450450
options.path = options.socketPath;
451451

‎lib/internal/http/client.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ const {
2626
Boolean,
2727
Error,
2828
NumberIsFinite,
29-
ObjectAssign,
3029
ObjectDefineProperty,
3130
ObjectHasOwn,
3231
ObjectKeys,
@@ -363,7 +362,8 @@ function ClientRequest(input, options, cb) {
363362
pathIsFromURL = false;
364363
}
365364

366-
options = ObjectAssign({ __proto__: null }, input, options);
365+
// Avoid creating a dictionary-mode object.
366+
options = ObjectSetPrototypeOf({ ...input, ...options }, null);
367367
}
368368

369369
// Propagate the user's highWaterMark to OutgoingMessage so that
@@ -411,7 +411,7 @@ function ClientRequest(input, options, cb) {
411411
const defaultPort = options.defaultPort ||
412412
(this.agent?.defaultPort);
413413

414-
const optsWithoutSignal = { __proto__: null, ...options };
414+
const optsWithoutSignal = ObjectSetPrototypeOf({ ...options }, null);
415415

416416
const port = optsWithoutSignal.port = options.port || defaultPort || 80;
417417
const host = optsWithoutSignal.host = validateHost(options.hostname, 'hostname') ||

0 commit comments

Comments
 (0)