Browse Source

http: speed up callbacks, use array indices

Use array indices rather than named properties to store callbacks on
the HTTPParser object.  Speeds up the http benchmarks by a few percent.
v0.11.6-release
Ben Noordhuis 12 years ago
parent
commit
2669966e76
  1. 14
      lib/_http_common.js
  2. 33
      src/node_http_parser.cc
  3. 13
      test/simple/test-http-parser-bad-ref.js
  4. 61
      test/simple/test-http-parser.js

14
lib/_http_common.js

@ -27,7 +27,6 @@ var IncomingMessage = incoming.IncomingMessage;
var readStart = incoming.readStart; var readStart = incoming.readStart;
var readStop = incoming.readStop; var readStop = incoming.readStop;
var debug = require('util').debuglog('http'); var debug = require('util').debuglog('http');
exports.debug = debug; exports.debug = debug;
@ -35,6 +34,11 @@ exports.CRLF = '\r\n';
exports.chunkExpression = /chunk/i; exports.chunkExpression = /chunk/i;
exports.continueExpression = /100-continue/i; exports.continueExpression = /100-continue/i;
var kOnHeaders = HTTPParser.kOnHeaders | 0;
var kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
var kOnBody = HTTPParser.kOnBody | 0;
var kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
// Only called in the slow case where slow means // Only called in the slow case where slow means
// that the request headers were either fragmented // that the request headers were either fragmented
// across multiple TCP packets or too large to be // across multiple TCP packets or too large to be
@ -180,10 +184,10 @@ var parsers = new FreeList('parsers', 1000, function() {
// across multiple TCP packets or too large to be // across multiple TCP packets or too large to be
// processed in a single run. This method is also // processed in a single run. This method is also
// called to process trailing HTTP headers. // called to process trailing HTTP headers.
parser.onHeaders = parserOnHeaders; parser[kOnHeaders] = parserOnHeaders;
parser.onHeadersComplete = parserOnHeadersComplete; parser[kOnHeadersComplete] = parserOnHeadersComplete;
parser.onBody = parserOnBody; parser[kOnBody] = parserOnBody;
parser.onMessageComplete = parserOnMessageComplete; parser[kOnMessageComplete] = parserOnMessageComplete;
return parser; return parser;
}); });

33
src/node_http_parser.cc

@ -60,10 +60,10 @@ using v8::Object;
using v8::String; using v8::String;
using v8::Value; using v8::Value;
static Cached<String> on_headers_sym; const uint32_t kOnHeaders = 0;
static Cached<String> on_headers_complete_sym; const uint32_t kOnHeadersComplete = 1;
static Cached<String> on_body_sym; const uint32_t kOnBody = 2;
static Cached<String> on_message_complete_sym; const uint32_t kOnMessageComplete = 3;
static Cached<String> method_sym; static Cached<String> method_sym;
static Cached<String> status_code_sym; static Cached<String> status_code_sym;
@ -255,7 +255,7 @@ class Parser : public ObjectWrap {
HTTP_CB(on_headers_complete) { HTTP_CB(on_headers_complete) {
Local<Object> obj = handle(node_isolate); Local<Object> obj = handle(node_isolate);
Local<Value> cb = obj->Get(on_headers_complete_sym); Local<Value> cb = obj->Get(kOnHeadersComplete);
if (!cb->IsFunction()) if (!cb->IsFunction())
return 0; return 0;
@ -315,7 +315,7 @@ class Parser : public ObjectWrap {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
Local<Object> obj = handle(node_isolate); Local<Object> obj = handle(node_isolate);
Local<Value> cb = obj->Get(on_body_sym); Local<Value> cb = obj->Get(kOnBody);
if (!cb->IsFunction()) if (!cb->IsFunction())
return 0; return 0;
@ -344,7 +344,7 @@ class Parser : public ObjectWrap {
Flush(); // Flush trailing HTTP headers. Flush(); // Flush trailing HTTP headers.
Local<Object> obj = handle(node_isolate); Local<Object> obj = handle(node_isolate);
Local<Value> cb = obj->Get(on_message_complete_sym); Local<Value> cb = obj->Get(kOnMessageComplete);
if (!cb->IsFunction()) if (!cb->IsFunction())
return 0; return 0;
@ -506,7 +506,7 @@ class Parser : public ObjectWrap {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
Local<Object> obj = handle(node_isolate); Local<Object> obj = handle(node_isolate);
Local<Value> cb = obj->Get(on_headers_sym); Local<Value> cb = obj->Get(kOnHeaders);
if (!cb->IsFunction()) if (!cb->IsFunction())
return; return;
@ -558,6 +558,14 @@ void InitHttpParser(Handle<Object> target) {
Integer::New(HTTP_REQUEST, node_isolate)); Integer::New(HTTP_REQUEST, node_isolate));
t->Set(FIXED_ONE_BYTE_STRING(node_isolate, "RESPONSE"), t->Set(FIXED_ONE_BYTE_STRING(node_isolate, "RESPONSE"),
Integer::New(HTTP_RESPONSE, node_isolate)); Integer::New(HTTP_RESPONSE, node_isolate));
t->Set(FIXED_ONE_BYTE_STRING(node_isolate, "kOnHeaders"),
Integer::NewFromUnsigned(kOnHeaders, node_isolate));
t->Set(FIXED_ONE_BYTE_STRING(node_isolate, "kOnHeadersComplete"),
Integer::NewFromUnsigned(kOnHeadersComplete, node_isolate));
t->Set(FIXED_ONE_BYTE_STRING(node_isolate, "kOnBody"),
Integer::NewFromUnsigned(kOnBody, node_isolate));
t->Set(FIXED_ONE_BYTE_STRING(node_isolate, "kOnMessageComplete"),
Integer::NewFromUnsigned(kOnMessageComplete, node_isolate));
NODE_SET_PROTOTYPE_METHOD(t, "execute", Parser::Execute); NODE_SET_PROTOTYPE_METHOD(t, "execute", Parser::Execute);
NODE_SET_PROTOTYPE_METHOD(t, "finish", Parser::Finish); NODE_SET_PROTOTYPE_METHOD(t, "finish", Parser::Finish);
@ -566,15 +574,6 @@ void InitHttpParser(Handle<Object> target) {
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "HTTPParser"), target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "HTTPParser"),
t->GetFunction()); t->GetFunction());
on_headers_sym =
FIXED_ONE_BYTE_STRING(node_isolate, "onHeaders");
on_headers_complete_sym =
FIXED_ONE_BYTE_STRING(node_isolate, "onHeadersComplete");
on_body_sym =
FIXED_ONE_BYTE_STRING(node_isolate, "onBody");
on_message_complete_sym =
FIXED_ONE_BYTE_STRING(node_isolate, "onMessageComplete");
#define X(num, name, string) \ #define X(num, name, string) \
name ## _sym = OneByteString(node_isolate, #string); name ## _sym = OneByteString(node_isolate, #string);
HTTP_METHOD_MAP(X) HTTP_METHOD_MAP(X)

13
test/simple/test-http-parser-bad-ref.js

@ -7,6 +7,11 @@ var common = require('../common');
var assert = require('assert'); var assert = require('assert');
var HTTPParser = process.binding('http_parser').HTTPParser; var HTTPParser = process.binding('http_parser').HTTPParser;
var kOnHeaders = HTTPParser.kOnHeaders | 0;
var kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
var kOnBody = HTTPParser.kOnBody | 0;
var kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
var headersComplete = 0; var headersComplete = 0;
var messagesComplete = 0; var messagesComplete = 0;
@ -23,19 +28,19 @@ function demoBug(part1, part2) {
parser.headers = []; parser.headers = [];
parser.url = ''; parser.url = '';
parser.onHeaders = function(headers, url) { parser[kOnHeaders] = function(headers, url) {
parser.headers = parser.headers.concat(headers); parser.headers = parser.headers.concat(headers);
parser.url += url; parser.url += url;
}; };
parser.onHeadersComplete = function(info) { parser[kOnHeadersComplete] = function(info) {
headersComplete++; headersComplete++;
console.log('url', info.url); console.log('url', info.url);
}; };
parser.onBody = function(b, start, len) { }; parser[kOnBody] = function(b, start, len) { };
parser.onMessageComplete = function() { parser[kOnMessageComplete] = function() {
messagesComplete++; messagesComplete++;
}; };

61
test/simple/test-http-parser.js

@ -28,6 +28,11 @@ var CRLF = '\r\n';
var REQUEST = HTTPParser.REQUEST; var REQUEST = HTTPParser.REQUEST;
var RESPONSE = HTTPParser.RESPONSE; var RESPONSE = HTTPParser.RESPONSE;
var kOnHeaders = HTTPParser.kOnHeaders | 0;
var kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
var kOnBody = HTTPParser.kOnBody | 0;
var kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
// The purpose of this test is not to check HTTP compliance but to test the // The purpose of this test is not to check HTTP compliance but to test the
// binding. Tests for pathological http messages should be submitted // binding. Tests for pathological http messages should be submitted
// upstream to https://github.com/joyent/http-parser for inclusion into // upstream to https://github.com/joyent/http-parser for inclusion into
@ -40,19 +45,19 @@ function newParser(type) {
parser.headers = []; parser.headers = [];
parser.url = ''; parser.url = '';
parser.onHeaders = function(headers, url) { parser[kOnHeaders] = function(headers, url) {
parser.headers = parser.headers.concat(headers); parser.headers = parser.headers.concat(headers);
parser.url += url; parser.url += url;
}; };
parser.onHeadersComplete = function(info) { parser[kOnHeadersComplete] = function(info) {
}; };
parser.onBody = function(b, start, len) { parser[kOnBody] = function(b, start, len) {
assert.ok(false, 'Function should not be called.'); assert.ok(false, 'Function should not be called.');
}; };
parser.onMessageComplete = function() { parser[kOnMessageComplete] = function() {
}; };
return parser; return parser;
@ -92,7 +97,7 @@ function expectBody(expected) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'GET'); assert.equal(info.method, 'GET');
assert.equal(info.url || parser.url, '/hello'); assert.equal(info.url || parser.url, '/hello');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
@ -106,7 +111,7 @@ function expectBody(expected) {
// thrown from parser.execute() // thrown from parser.execute()
// //
parser.onHeadersComplete = function(info) { parser[kOnHeadersComplete] = function(info) {
throw new Error('hello world'); throw new Error('hello world');
}; };
@ -131,14 +136,14 @@ function expectBody(expected) {
var parser = newParser(RESPONSE); var parser = newParser(RESPONSE);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, undefined); assert.equal(info.method, undefined);
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
assert.equal(info.versionMinor, 1); assert.equal(info.versionMinor, 1);
assert.equal(info.statusCode, 200); assert.equal(info.statusCode, 200);
}); });
parser.onBody = mustCall(function(buf, start, len) { parser[kOnBody] = mustCall(function(buf, start, len) {
var body = '' + buf.slice(start, start + len); var body = '' + buf.slice(start, start + len);
assert.equal(body, 'pong'); assert.equal(body, 'pong');
}); });
@ -157,7 +162,7 @@ function expectBody(expected) {
var parser = newParser(RESPONSE); var parser = newParser(RESPONSE);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, undefined); assert.equal(info.method, undefined);
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
assert.equal(info.versionMinor, 0); assert.equal(info.versionMinor, 0);
@ -194,16 +199,16 @@ function expectBody(expected) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'POST'); assert.equal(info.method, 'POST');
assert.equal(info.url || parser.url, '/it'); assert.equal(info.url || parser.url, '/it');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
assert.equal(info.versionMinor, 1); assert.equal(info.versionMinor, 1);
// expect to see trailing headers now // expect to see trailing headers now
parser.onHeaders = mustCall(onHeaders); parser[kOnHeaders] = mustCall(onHeaders);
}); });
parser.onBody = mustCall(function(buf, start, len) { parser[kOnBody] = mustCall(function(buf, start, len) {
var body = '' + buf.slice(start, start + len); var body = '' + buf.slice(start, start + len);
assert.equal(body, 'ping'); assert.equal(body, 'ping');
seen_body = true; seen_body = true;
@ -226,7 +231,7 @@ function expectBody(expected) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'GET'); assert.equal(info.method, 'GET');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
assert.equal(info.versionMinor, 0); assert.equal(info.versionMinor, 0);
@ -255,7 +260,7 @@ function expectBody(expected) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'GET'); assert.equal(info.method, 'GET');
assert.equal(info.url || parser.url, '/foo/bar/baz?quux=42#1337'); assert.equal(info.url || parser.url, '/foo/bar/baz?quux=42#1337');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
@ -287,14 +292,14 @@ function expectBody(expected) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'POST'); assert.equal(info.method, 'POST');
assert.equal(info.url || parser.url, '/it'); assert.equal(info.url || parser.url, '/it');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
assert.equal(info.versionMinor, 1); assert.equal(info.versionMinor, 1);
}); });
parser.onBody = mustCall(function(buf, start, len) { parser[kOnBody] = mustCall(function(buf, start, len) {
var body = '' + buf.slice(start, start + len); var body = '' + buf.slice(start, start + len);
assert.equal(body, 'foo=42&bar=1337'); assert.equal(body, 'foo=42&bar=1337');
}); });
@ -322,7 +327,7 @@ function expectBody(expected) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'POST'); assert.equal(info.method, 'POST');
assert.equal(info.url || parser.url, '/it'); assert.equal(info.url || parser.url, '/it');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
@ -337,7 +342,7 @@ function expectBody(expected) {
assert.equal(body, body_parts[body_part++]); assert.equal(body, body_parts[body_part++]);
} }
parser.onBody = mustCall(onBody, body_parts.length); parser[kOnBody] = mustCall(onBody, body_parts.length);
parser.execute(request, 0, request.length); parser.execute(request, 0, request.length);
})(); })();
@ -358,7 +363,7 @@ function expectBody(expected) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'POST'); assert.equal(info.method, 'POST');
assert.equal(info.url || parser.url, '/it'); assert.equal(info.url || parser.url, '/it');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
@ -375,7 +380,7 @@ function expectBody(expected) {
assert.equal(body, body_parts[body_part++]); assert.equal(body, body_parts[body_part++]);
} }
parser.onBody = mustCall(onBody, body_parts.length); parser[kOnBody] = mustCall(onBody, body_parts.length);
parser.execute(request, 0, request.length); parser.execute(request, 0, request.length);
request = Buffer( request = Buffer(
@ -415,7 +420,7 @@ function expectBody(expected) {
function test(a, b) { function test(a, b) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'POST'); assert.equal(info.method, 'POST');
assert.equal(info.url || parser.url, '/helpme'); assert.equal(info.url || parser.url, '/helpme');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
@ -424,7 +429,7 @@ function expectBody(expected) {
var expected_body = '123123456123456789123456789ABC123456789ABCDEF'; var expected_body = '123123456123456789123456789ABC123456789ABCDEF';
parser.onBody = function(buf, start, len) { parser[kOnBody] = function(buf, start, len) {
var chunk = '' + buf.slice(start, start + len); var chunk = '' + buf.slice(start, start + len);
assert.equal(expected_body.indexOf(chunk), 0); assert.equal(expected_body.indexOf(chunk), 0);
expected_body = expected_body.slice(chunk.length); expected_body = expected_body.slice(chunk.length);
@ -471,7 +476,7 @@ function expectBody(expected) {
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = mustCall(function(info) { parser[kOnHeadersComplete] = mustCall(function(info) {
assert.equal(info.method, 'POST'); assert.equal(info.method, 'POST');
assert.equal(info.url || parser.url, '/it'); assert.equal(info.url || parser.url, '/it');
assert.equal(info.versionMajor, 1); assert.equal(info.versionMajor, 1);
@ -483,7 +488,7 @@ function expectBody(expected) {
var expected_body = '123123456123456789123456789ABC123456789ABCDEF'; var expected_body = '123123456123456789123456789ABC123456789ABCDEF';
parser.onBody = function(buf, start, len) { parser[kOnBody] = function(buf, start, len) {
var chunk = '' + buf.slice(start, start + len); var chunk = '' + buf.slice(start, start + len);
assert.equal(expected_body.indexOf(chunk), 0); assert.equal(expected_body.indexOf(chunk), 0);
expected_body = expected_body.slice(chunk.length); expected_body = expected_body.slice(chunk.length);
@ -538,12 +543,12 @@ function expectBody(expected) {
}; };
var parser = newParser(REQUEST); var parser = newParser(REQUEST);
parser.onHeadersComplete = onHeadersComplete1; parser[kOnHeadersComplete] = onHeadersComplete1;
parser.onBody = expectBody('ping'); parser[kOnBody] = expectBody('ping');
parser.execute(req1, 0, req1.length); parser.execute(req1, 0, req1.length);
parser.reinitialize(REQUEST); parser.reinitialize(REQUEST);
parser.onBody = expectBody('pong'); parser[kOnBody] = expectBody('pong');
parser.onHeadersComplete = onHeadersComplete2; parser[kOnHeadersComplete] = onHeadersComplete2;
parser.execute(req2, 0, req2.length); parser.execute(req2, 0, req2.length);
})(); })();

Loading…
Cancel
Save