Skip to content

Commit

Permalink
http2: improve compat performance
Browse files Browse the repository at this point in the history
This bunch of commits help me improve the performance of a http2
server by 8-10%. The benchmarks reports several 1-2% improvements in
various areas.
  • Loading branch information
mcollina committed Jan 18, 2019
1 parent 228a3f8 commit b098fed
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 10 deletions.
35 changes: 35 additions & 0 deletions benchmark/http2/compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

const common = require('../common.js');
const path = require('path');
const fs = require('fs');
const file = path.join(path.resolve(__dirname, '../fixtures'), 'alice.html');

const bench = common.createBenchmark(main, {
requests: [100, 1000, 5000],
streams: [1, 10, 20, 40, 100, 200],
clients: [2],
benchmarker: ['h2load']
}, { flags: ['--no-warnings'] });

function main({ requests, streams, clients }) {
const http2 = require('http2');
const server = http2.createServer();
server.on('request', (req, res) => {
const out = fs.createReadStream(file);
res.setHeader('content-type', 'text/html');
out.pipe(res);
out.on('error', (err) => {
res.destroy();
});
});
server.listen(common.PORT, () => {
bench.http({
path: '/',
requests,
maxConcurrentStreams: streams,
clients,
threads: clients
}, () => { server.close(); });
});
}
4 changes: 1 addition & 3 deletions lib/internal/http2/compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,16 @@ const {
ERR_INVALID_HTTP_TOKEN
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
const { kSocket } = require('internal/http2/util');
const { kSocket, kRequest, kProxySocket } = require('internal/http2/util');

const kBeginSend = Symbol('begin-send');
const kState = Symbol('state');
const kStream = Symbol('stream');
const kRequest = Symbol('request');
const kResponse = Symbol('response');
const kHeaders = Symbol('headers');
const kRawHeaders = Symbol('rawHeaders');
const kTrailers = Symbol('trailers');
const kRawTrailers = Symbol('rawTrailers');
const kProxySocket = Symbol('proxySocket');
const kSetHeader = Symbol('setHeader');
const kAborted = Symbol('aborted');

Expand Down
26 changes: 22 additions & 4 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ const {
getStreamState,
isPayloadMeaningless,
kSocket,
kRequest,
kProxySocket,
mapToHeaders,
NghttpError,
sessionName,
Expand All @@ -119,6 +121,8 @@ const {
} = require('internal/timers');
const { isArrayBufferView } = require('internal/util/types');

const hasOwnProperty = Object.prototype.hasOwnProperty;

const { FileHandle } = internalBinding('fs');
const binding = internalBinding('http2');
const {
Expand Down Expand Up @@ -157,7 +161,6 @@ const kOwner = owner_symbol;
const kOrigin = Symbol('origin');
const kProceed = Symbol('proceed');
const kProtocol = Symbol('protocol');
const kProxySocket = Symbol('proxy-socket');
const kRemoteSettings = Symbol('remote-settings');
const kSelectPadding = Symbol('select-padding');
const kSentHeaders = Symbol('sent-headers');
Expand Down Expand Up @@ -1624,6 +1627,10 @@ class Http2Stream extends Duplex {
endAfterHeaders: false
};

// Fields used by the compat API to avoid megamorphisms.
this[kRequest] = null;
this[kProxySocket] = null;

this.on('pause', streamOnPause);
}

Expand Down Expand Up @@ -2001,9 +2008,20 @@ class Http2Stream extends Duplex {
}
}

function processHeaders(headers) {
assertIsObject(headers, 'headers');
headers = Object.assign(Object.create(null), headers);
function processHeaders(oldHeaders) {
assertIsObject(oldHeaders, 'headers');
const headers = Object.create(null);

if (oldHeaders !== null && oldHeaders !== undefined) {
const hop = hasOwnProperty.bind(oldHeaders);
// This loop is here for performance reason. Do not change.
for (var key in oldHeaders) {
if (hop(key)) {
headers[key] = oldHeaders[key];
}
}
}

const statusCode =
headers[HTTP2_HEADER_STATUS] =
headers[HTTP2_HEADER_STATUS] | 0 || HTTP_STATUS_OK;
Expand Down
8 changes: 6 additions & 2 deletions lib/internal/http2/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const {
} = require('internal/errors').codes;

const kSocket = Symbol('socket');
const kProxySocket = Symbol('proxySocket');
const kRequest = Symbol('request');

const {
NGHTTP2_SESSION_CLIENT,
Expand Down Expand Up @@ -499,12 +501,12 @@ class NghttpError extends Error {
}
}

function assertIsObject(value, name, types = 'Object') {
function assertIsObject(value, name, types) {
if (value !== undefined &&
(value === null ||
typeof value !== 'object' ||
Array.isArray(value))) {
const err = new ERR_INVALID_ARG_TYPE(name, types, value);
const err = new ERR_INVALID_ARG_TYPE(name, types || 'Object', value);
Error.captureStackTrace(err, assertIsObject);
throw err;
}
Expand Down Expand Up @@ -592,6 +594,8 @@ module.exports = {
getStreamState,
isPayloadMeaningless,
kSocket,
kProxySocket,
kRequest,
mapToHeaders,
NghttpError,
sessionName,
Expand Down
20 changes: 19 additions & 1 deletion src/stream_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,29 @@ void LibuvStreamWrap::Initialize(Local<Object> target,
};
Local<FunctionTemplate> sw =
FunctionTemplate::New(env->isolate(), is_construct_call_callback);
sw->InstanceTemplate()->SetInternalFieldCount(StreamReq::kStreamReqField + 1);
sw->InstanceTemplate()->SetInternalFieldCount(
StreamReq::kStreamReqField + 1 + 3);
Local<String> wrapString =
FIXED_ONE_BYTE_STRING(env->isolate(), "ShutdownWrap");
sw->SetClassName(wrapString);

// we need to set handle and callback to null,
// so that those fields are created and functions
// do not become megamorphic
// Fields:
// - oncomplete
// - callback
// - handle
sw->InstanceTemplate()->Set(
FIXED_ONE_BYTE_STRING(env->isolate(), "oncomplete"),
v8::Null(env->isolate()));
sw->InstanceTemplate()->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "callback"),
v8::Null(env->isolate()));
sw->InstanceTemplate()->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "handle"),
v8::Null(env->isolate()));

sw->Inherit(AsyncWrap::GetConstructorTemplate(env));

target->Set(env->context(),
wrapString,
sw->GetFunction(env->context()).ToLocalChecked()).FromJust();
Expand Down

0 comments on commit b098fed

Please sign in to comment.