GeezerVison is showing github.com — http: improve performance with known-length calls to end() · nodejs/node@fa46008
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +35Lines changed: 35 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// Responses sent as a single res.end(string) with a known Content-Length - | | 2 | +// the shape a JSON or HTML endpoint produces. | | 3 | +'use strict'; | | 4 | + | | 5 | +const common = require('../common.js'); | | 6 | + | | 7 | +const bench = common.createBenchmark(main, { | | 8 | + len: [4, 64, 1024, 16384, 102400], | | 9 | + c: [50], | | 10 | + duration: 5, | | 11 | +}); | | 12 | + | | 13 | +function main({ len, c, duration }) { | | 14 | + const http = require('http'); | | 15 | + const body = 'a'.repeat(len); | | 16 | + const headers = { | | 17 | + 'Content-Type': 'text/plain', | | 18 | + 'Content-Length': `${len}`, | | 19 | + }; | | 20 | + | | 21 | + const server = http.createServer((req, res) => { | | 22 | + res.writeHead(200, headers); | | 23 | + res.end(body); | | 24 | + }); | | 25 | + | | 26 | + server.listen(0, () => { | | 27 | + bench.http({ | | 28 | + connections: c, | | 29 | + duration, | | 30 | + port: server.address().port, | | 31 | + }, () => { | | 32 | + server.close(); | | 33 | + }); | | 34 | + }); | | 35 | +} |
Collapse file Copy file name to clipboardExpand all lines: lib/_http_outgoing.js +43-8Lines changed: 43 additions & 8 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1026,6 +1026,25 @@ function write_(msg, chunk, encoding, callback, fromEnd) { | 1026 | 1026 | } | 1027 | 1027 |
| 1028 | 1028 |
| | 1029 | +// If this last write can be delivered immediately as the final chunk, this | | 1030 | +// prepares to do so, and then returns true. If not, it returns false and | | 1031 | +// a separate _send call and tick will be required to finish up. | | 1032 | +function maybePrepareFinalChunk(msg, chunk, encoding) { | | 1033 | + if (typeof chunk !== 'string' && !isUint8Array(chunk)) | | 1034 | + return false; | | 1035 | + | | 1036 | + if (msg.destroyed || msg.strictContentLength) | | 1037 | + return false; | | 1038 | + | | 1039 | + if (!msg._header) { | | 1040 | + msg._contentLength = typeof chunk === 'string' ? | | 1041 | + Buffer.byteLength(chunk, encoding) : chunk.byteLength; | | 1042 | + msg._implicitHeader(); | | 1043 | + } | | 1044 | + | | 1045 | + return !!msg._header && msg._hasBody && !msg.chunkedEncoding; | | 1046 | +} | | 1047 | + | 1029 | 1048 | function connectionCorkNT(conn) { | 1030 | 1049 | conn.uncork(); | 1031 | 1050 | } | @@ -1131,6 +1150,8 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { | 1131 | 1150 | encoding = null; | 1132 | 1151 | } | 1133 | 1152 |
| | 1153 | + let finishCallback = null; | | 1154 | + | 1134 | 1155 | if (chunk) { | 1135 | 1156 | if (this.finished) { | 1136 | 1157 | onError(this, | @@ -1143,7 +1164,18 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { | 1143 | 1164 | this[kSocket].cork(); | 1144 | 1165 | } | 1145 | 1166 |
| 1146 | | - write_(this, chunk, encoding, null, true); | | 1167 | + if (maybePrepareFinalChunk(this, chunk, encoding)) { | | 1168 | + // If just one final write is required, with nothing to follow, we | | 1169 | + // attach finish to the write to avoid a separate send() & tick step | | 1170 | + // later on - this is purely a performance optimization. | | 1171 | + if (typeof callback === 'function') { | | 1172 | + queueEndCallback(this, callback); | | 1173 | + callback = undefined; | | 1174 | + } | | 1175 | + finishCallback = onFinish.bind(undefined, this); | | 1176 | + } | | 1177 | + | | 1178 | + write_(this, chunk, encoding, finishCallback, true); | 1147 | 1179 | } else if (this.finished) { | 1148 | 1180 | if (typeof callback === 'function') { | 1149 | 1181 | queueEndCallback(this, callback); | @@ -1165,14 +1197,17 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { | 1165 | 1197 | throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], this._contentLength); | 1166 | 1198 | } | 1167 | 1199 |
| 1168 | | - const finish = onFinish.bind(undefined, this); | | 1200 | + if (finishCallback === null) { | | 1201 | + // If we didn't early finish, send the last data and schedule 'finish' now: | | 1202 | + finishCallback = onFinish.bind(undefined, this); | 1169 | 1203 |
| 1170 | | - if (this._hasBody && this.chunkedEncoding) { | 1171 | | - this._send('0\r\n' + this._trailer + '\r\n', 'latin1', finish); | 1172 | | - } else if (!this._headerSent || this.writableLength || chunk) { | 1173 | | - this._send('', 'latin1', finish); | 1174 | | - } else { | 1175 | | - process.nextTick(finish); | | 1204 | + if (this._hasBody && this.chunkedEncoding) { | | 1205 | + this._send('0\r\n' + this._trailer + '\r\n', 'latin1', finishCallback); | | 1206 | + } else if (!this._headerSent || this.writableLength || chunk) { | | 1207 | + this._send('', 'latin1', finishCallback); | | 1208 | + } else { | | 1209 | + process.nextTick(finishCallback); | | 1210 | + } | 1176 | 1211 | } | 1177 | 1212 |
| 1178 | 1213 | if (this[kSocket]) { | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-http-server-response-standalone.js +2-9Lines changed: 2 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -15,18 +15,11 @@ const res = new ServerResponse({ | 15 | 15 | httpVersionMinor: 1 | 16 | 16 | }); | 17 | 17 |
| 18 | | -let firstChunk = true; | 19 | | - | 20 | 18 | const ws = new Writable({ | 21 | 19 | write: common.mustCall((chunk, encoding, callback) => { | 22 | | - if (firstChunk) { | 23 | | - assert(chunk.toString().endsWith('hello world')); | 24 | | - firstChunk = false; | 25 | | - } else { | 26 | | - assert.strictEqual(chunk.length, 0); | 27 | | - } | | 20 | + assert(chunk.toString().endsWith('hello world')); | 28 | 21 | setImmediate(callback); | 29 | | - }, 2) | | 22 | + }, 1) | 30 | 23 | }); | 31 | 24 |
| 32 | 25 | res.assignSocket(ws); | |
|