FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

http: improve performance with known-length calls to end() · nodejs/node@c2c31d6 · GitHub

/ node Public

Commit c2c31d6

Browse files
authored andcommitted
http: improve performance with known-length calls to end()
This boosts RPS performance for the common API case where you call `res.end(data)` with the entire response by up to 9%. Signed-off-by: Tim Perry <pimterry@gmail.com> PR-URL: #65466 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
1 parent 6409983 commit c2c31d6

3 files changed

Lines changed: 80 additions & 17 deletions

File tree

‎benchmark/http/end-string.js‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
}

‎lib/_http_outgoing.js‎

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,25 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
10261026
}
10271027

10281028

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+
10291048
function connectionCorkNT(conn) {
10301049
conn.uncork();
10311050
}
@@ -1131,6 +1150,8 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
11311150
encoding = null;
11321151
}
11331152

1153+
let finishCallback = null;
1154+
11341155
if (chunk) {
11351156
if (this.finished) {
11361157
onError(this,
@@ -1143,7 +1164,18 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
11431164
this[kSocket].cork();
11441165
}
11451166

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);
11471179
} else if (this.finished) {
11481180
if (typeof callback === 'function') {
11491181
queueEndCallback(this, callback);
@@ -1165,14 +1197,17 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
11651197
throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], this._contentLength);
11661198
}
11671199

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);
11691203

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+
}
11761211
}
11771212

11781213
if (this[kSocket]) {

‎test/parallel/test-http-server-response-standalone.js‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,11 @@ const res = new ServerResponse({
1515
httpVersionMinor: 1
1616
});
1717

18-
let firstChunk = true;
19-
2018
const ws = new Writable({
2119
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'));
2821
setImmediate(callback);
29-
}, 2)
22+
}, 1)
3023
});
3124

3225
res.assignSocket(ws);

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL