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.

PR-URL: #25567
Reviewed-By: Benedikt Meurer <benedikt.meurer@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
mcollina authored and danbev committed Feb 11, 2019
1 parent fcaeb1f commit 9af04ad
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 @@ -117,6 +119,8 @@ const {
const { kTimeout } = 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 @@ -155,7 +159,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 @@ -1622,6 +1625,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 @@ -64,11 +64,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);

This comment has been minimized.

Copy link
@addaleax

addaleax Mar 20, 2019

Member

@mcollina Do you remember why you did this change? I think it could be undone, right?

This comment has been minimized.

Copy link
@mcollina

mcollina Mar 21, 2019

Author Member

Because I've added the three following fields later in this PR.
The reason for doing this here is to make sure some of the functions do not become megamorphic.

Why do you want to remove it?

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 9af04ad

Please sign in to comment.