diff --git a/benchmark/fixtures/simple-http-server.js b/benchmark/fixtures/simple-http-server.js index fb5b0d87c16e..808ee7e4b2dc 100644 --- a/benchmark/fixtures/simple-http-server.js +++ b/benchmark/fixtures/simple-http-server.js @@ -114,6 +114,12 @@ module.exports = http.createServer((req, res) => { else res.writeHead(status, { 'Content-Length': len.toString() }); break; + case 'setHeaderImplicit': + // Let end() derive the message framing: no Content-Length or + // Transfer-Encoding header and no writeHead() call. + res.statusCode = status; + res.setHeader('Content-Type', 'text/plain'); + break; default: if (chunkedEnc) { res.writeHead(status, { diff --git a/benchmark/http/set-header.js b/benchmark/http/set-header.js index 8bf00b84ad0a..0936c1309e2d 100644 --- a/benchmark/http/set-header.js +++ b/benchmark/http/set-header.js @@ -2,7 +2,7 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { - res: ['normal', 'setHeader', 'setHeaderWH'], + res: ['normal', 'setHeader', 'setHeaderWH', 'setHeaderImplicit'], duration: 5, }); @@ -15,6 +15,7 @@ const c = 50; // normal: writeHead(status, {...}) // setHeader: statusCode = status, setHeader(...) x2 // setHeaderWH: setHeader(...), writeHead(status, ...) +// setHeaderImplicit: statusCode = status, setHeader(...) x1, framing left to end() function main({ res, duration }) { const server = require('../fixtures/simple-http-server.js') .listen(0) diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index f6713d7b06a4..2ca455eb5566 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -87,6 +87,15 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => { }); const kCorked = Symbol('corked'); + +// Container for the headers set through `setHeader()`, keyed by the +// lower-cased header name. It has no enumerable or inherited properties, like +// a `{ __proto__: null }` literal, but instances are fast-mode objects: V8 +// creates null-prototype literals in dictionary mode, which costs a hash table +// allocation per response and slower property access for every header. +class OutgoingHeaders {} +ObjectSetPrototypeOf(OutgoingHeaders.prototype, null); +delete OutgoingHeaders.prototype.constructor; const kSocket = Symbol('kSocket'); const kChunkedBuffer = Symbol('kChunkedBuffer'); const kChunkedLength = Symbol('kChunkedLength'); @@ -124,6 +133,18 @@ function OutgoingMessage(options) { return deprecateInstantiation(OutgoingMessage, 'DEP0195', options); } + // Preset the listener table with the events that are commonly listened + // for, mirroring streams. The presence of the properties keeps `_events` + // in fast mode: the first `on()` call would otherwise have to allocate a + // dictionary for the `{ __proto__: null }` object created by EventEmitter. + this._events ??= { + close: undefined, + error: undefined, + finish: undefined, + prefinish: undefined, + drain: undefined, + }; + Stream.call(this); // Queue that holds all currently pending data, until the response will be @@ -805,7 +826,7 @@ OutgoingMessage.prototype.setHeader = function setHeader(name, value) { let headers = this[kOutHeaders]; if (headers === null) - this[kOutHeaders] = headers = { __proto__: null }; + this[kOutHeaders] = headers = new OutgoingHeaders(); headers[name.toLowerCase()] = [name, value]; return this;