summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Noordhuis <info@bnoordhuis.nl>2013-05-22 21:43:35 +0200
committerBen Noordhuis <info@bnoordhuis.nl>2013-05-23 02:13:26 +0200
commitfda2b319dc8a5dae528f8b12096911b71a0282db (patch)
tree56f50e37e5cd57f673240875765e7e7d73165dcf
parent89dcf22526044918ae3f79f6728ad4e603a1d710 (diff)
downloadandroid-node-v8-fda2b319dc8a5dae528f8b12096911b71a0282db.tar.gz
android-node-v8-fda2b319dc8a5dae528f8b12096911b71a0282db.tar.bz2
android-node-v8-fda2b319dc8a5dae528f8b12096911b71a0282db.zip
http: save roundtrips, convert buffers to strings
This commit adds an optimization to the HTTP client that makes it possible to: * Pack the headers and the first chunk of the request body into a single write(). * Pack the chunk header and the chunk itself into a single write(). Because only one write() system call is issued instead of several, the chances of data ending up in a single TCP packet are phenomenally higher: the benchmark with `type=buf size=32` jumps from 50 req/s to 7,500 req/s, a 150-fold increase. This commit removes the check from e4b716ef that pushes binary encoded strings into the slow path. The commit log mentions that: We were assuming that any string can be concatenated safely to CRLF. However, for hex, base64, or binary encoded writes, this is not the case, and results in sending the incorrect response. For hex and base64 strings that's certainly true but binary strings are 'das Ding an sich': string.length is the same before and after decoding. Fixes #5528.
-rw-r--r--benchmark/http/client-request-body.js69
-rw-r--r--lib/http.js20
2 files changed, 87 insertions, 2 deletions
diff --git a/benchmark/http/client-request-body.js b/benchmark/http/client-request-body.js
new file mode 100644
index 0000000000..7a3468a670
--- /dev/null
+++ b/benchmark/http/client-request-body.js
@@ -0,0 +1,69 @@
+// Measure the time it takes for the HTTP client to send a request body.
+
+var common = require('../common.js');
+var http = require('http');
+
+var bench = common.createBenchmark(main, {
+ dur: [5],
+ type: ['asc', 'utf', 'buf'],
+ bytes: [32, 256, 1024],
+ method: ['write', 'end '] // two spaces added to line up each row
+});
+
+function main(conf) {
+ var dur = +conf.dur;
+ var len = +conf.bytes;
+
+ var encoding;
+ var chunk;
+ switch (conf.type) {
+ case 'buf':
+ chunk = new Buffer(len);
+ chunk.fill('x');
+ break;
+ case 'utf':
+ encoding = 'utf8';
+ chunk = new Array(len / 2 + 1).join('ΓΌ');
+ break;
+ case 'asc':
+ chunk = new Array(len + 1).join('a');
+ break;
+ }
+
+ var nreqs = 0;
+ var options = {
+ headers: { 'Connection': 'keep-alive', 'Transfer-Encoding': 'chunked' },
+ agent: new http.Agent({ maxSockets: 1 }),
+ host: '127.0.0.1',
+ port: common.PORT,
+ path: '/',
+ method: 'POST'
+ };
+
+ var server = http.createServer(function(req, res) {
+ res.end();
+ });
+ server.listen(options.port, options.host, function() {
+ setTimeout(done, dur * 1000);
+ bench.start();
+ pummel();
+ });
+
+ function pummel() {
+ var req = http.request(options, function(res) {
+ nreqs++;
+ pummel(); // Line up next request.
+ res.resume();
+ });
+ if (conf.method === 'write') {
+ req.write(chunk, encoding);
+ req.end();
+ } else {
+ req.end(chunk, encoding);
+ }
+ }
+
+ function done() {
+ bench.end(nreqs);
+ }
+}
diff --git a/lib/http.js b/lib/http.js
index 4a4fe94f3e..bcde516dae 100644
--- a/lib/http.js
+++ b/lib/http.js
@@ -782,12 +782,28 @@ OutgoingMessage.prototype.write = function(chunk, encoding) {
if (chunk.length === 0) return false;
+ // TODO(bnoordhuis) Temporary optimization hack, remove in v0.11. We only
+ // want to convert the buffer when we're sending:
+ //
+ // a) Transfer-Encoding chunks, because it lets us pack the chunk header
+ // and the chunk into a single write(), or
+ //
+ // b) the first chunk of a fixed-length request, because it lets us pack
+ // the request headers and the chunk into a single write().
+ //
+ // Converting to strings is expensive, CPU-wise, but reducing the number
+ // of write() calls more than makes up for that because we're dramatically
+ // reducing the number of TCP roundtrips.
+ if (chunk instanceof Buffer && (this.chunkedEncoding || !this._headerSent)) {
+ chunk = chunk.toString('binary');
+ encoding = 'binary';
+ }
+
var len, ret;
if (this.chunkedEncoding) {
if (typeof(chunk) === 'string' &&
encoding !== 'hex' &&
- encoding !== 'base64' &&
- encoding !== 'binary') {
+ encoding !== 'base64') {
len = Buffer.byteLength(chunk, encoding);
chunk = len.toString(16) + CRLF + chunk + CRLF;
ret = this._send(chunk, encoding);