Skip to content

Commit 0899c8b

Browse files
mcollinatargos
authored andcommitted
http2: improve compat performance
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>
1 parent d5d163d commit 0899c8b

File tree

5 files changed

+83
-10
lines changed

5 files changed

+83
-10
lines changed

benchmark/http2/compat.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const path = require('path');
5+
const fs = require('fs');
6+
const file = path.join(path.resolve(__dirname, '../fixtures'), 'alice.html');
7+
8+
const bench = common.createBenchmark(main, {
9+
requests: [100, 1000, 5000],
10+
streams: [1, 10, 20, 40, 100, 200],
11+
clients: [2],
12+
benchmarker: ['h2load']
13+
}, { flags: ['--no-warnings'] });
14+
15+
function main({ requests, streams, clients }) {
16+
const http2 = require('http2');
17+
const server = http2.createServer();
18+
server.on('request', (req, res) => {
19+
const out = fs.createReadStream(file);
20+
res.setHeader('content-type', 'text/html');
21+
out.pipe(res);
22+
out.on('error', (err) => {
23+
res.destroy();
24+
});
25+
});
26+
server.listen(common.PORT, () => {
27+
bench.http({
28+
path: '/',
29+
requests,
30+
maxConcurrentStreams: streams,
31+
clients,
32+
threads: clients
33+
}, () => { server.close(); });
34+
});
35+
}

lib/internal/http2/compat.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,16 @@ const {
1818
ERR_INVALID_HTTP_TOKEN
1919
} = require('internal/errors').codes;
2020
const { validateString } = require('internal/validators');
21-
const { kSocket } = require('internal/http2/util');
21+
const { kSocket, kRequest, kProxySocket } = require('internal/http2/util');
2222

2323
const kBeginSend = Symbol('begin-send');
2424
const kState = Symbol('state');
2525
const kStream = Symbol('stream');
26-
const kRequest = Symbol('request');
2726
const kResponse = Symbol('response');
2827
const kHeaders = Symbol('headers');
2928
const kRawHeaders = Symbol('rawHeaders');
3029
const kTrailers = Symbol('trailers');
3130
const kRawTrailers = Symbol('rawTrailers');
32-
const kProxySocket = Symbol('proxySocket');
3331
const kSetHeader = Symbol('setHeader');
3432
const kAborted = Symbol('aborted');
3533

lib/internal/http2/core.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ const {
9696
getStreamState,
9797
isPayloadMeaningless,
9898
kSocket,
99+
kRequest,
100+
kProxySocket,
99101
mapToHeaders,
100102
NghttpError,
101103
sessionName,
@@ -119,6 +121,8 @@ const {
119121
} = require('internal/timers');
120122
const { isArrayBufferView } = require('internal/util/types');
121123

124+
const hasOwnProperty = Object.prototype.hasOwnProperty;
125+
122126
const { FileHandle } = internalBinding('fs');
123127
const binding = internalBinding('http2');
124128
const {
@@ -157,7 +161,6 @@ const kOwner = owner_symbol;
157161
const kOrigin = Symbol('origin');
158162
const kProceed = Symbol('proceed');
159163
const kProtocol = Symbol('protocol');
160-
const kProxySocket = Symbol('proxy-socket');
161164
const kRemoteSettings = Symbol('remote-settings');
162165
const kSelectPadding = Symbol('select-padding');
163166
const kSentHeaders = Symbol('sent-headers');
@@ -1625,6 +1628,10 @@ class Http2Stream extends Duplex {
16251628
endAfterHeaders: false
16261629
};
16271630

1631+
// Fields used by the compat API to avoid megamorphisms.
1632+
this[kRequest] = null;
1633+
this[kProxySocket] = null;
1634+
16281635
this.on('pause', streamOnPause);
16291636
}
16301637

@@ -2004,9 +2011,20 @@ class Http2Stream extends Duplex {
20042011
}
20052012
}
20062013

2007-
function processHeaders(headers) {
2008-
assertIsObject(headers, 'headers');
2009-
headers = Object.assign(Object.create(null), headers);
2014+
function processHeaders(oldHeaders) {
2015+
assertIsObject(oldHeaders, 'headers');
2016+
const headers = Object.create(null);
2017+
2018+
if (oldHeaders !== null && oldHeaders !== undefined) {
2019+
const hop = hasOwnProperty.bind(oldHeaders);
2020+
// This loop is here for performance reason. Do not change.
2021+
for (var key in oldHeaders) {
2022+
if (hop(key)) {
2023+
headers[key] = oldHeaders[key];
2024+
}
2025+
}
2026+
}
2027+
20102028
const statusCode =
20112029
headers[HTTP2_HEADER_STATUS] =
20122030
headers[HTTP2_HEADER_STATUS] | 0 || HTTP_STATUS_OK;

lib/internal/http2/util.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const {
1010
} = require('internal/errors').codes;
1111

1212
const kSocket = Symbol('socket');
13+
const kProxySocket = Symbol('proxySocket');
14+
const kRequest = Symbol('request');
1315

1416
const {
1517
NGHTTP2_SESSION_CLIENT,
@@ -499,12 +501,12 @@ class NghttpError extends Error {
499501
}
500502
}
501503

502-
function assertIsObject(value, name, types = 'Object') {
504+
function assertIsObject(value, name, types) {
503505
if (value !== undefined &&
504506
(value === null ||
505507
typeof value !== 'object' ||
506508
Array.isArray(value))) {
507-
const err = new ERR_INVALID_ARG_TYPE(name, types, value);
509+
const err = new ERR_INVALID_ARG_TYPE(name, types || 'Object', value);
508510
Error.captureStackTrace(err, assertIsObject);
509511
throw err;
510512
}
@@ -592,6 +594,8 @@ module.exports = {
592594
getStreamState,
593595
isPayloadMeaningless,
594596
kSocket,
597+
kProxySocket,
598+
kRequest,
595599
mapToHeaders,
596600
NghttpError,
597601
sessionName,

src/stream_wrap.cc

+19-1
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,29 @@ void LibuvStreamWrap::Initialize(Local<Object> target,
6464
};
6565
Local<FunctionTemplate> sw =
6666
FunctionTemplate::New(env->isolate(), is_construct_call_callback);
67-
sw->InstanceTemplate()->SetInternalFieldCount(StreamReq::kStreamReqField + 1);
67+
sw->InstanceTemplate()->SetInternalFieldCount(
68+
StreamReq::kStreamReqField + 1 + 3);
6869
Local<String> wrapString =
6970
FIXED_ONE_BYTE_STRING(env->isolate(), "ShutdownWrap");
7071
sw->SetClassName(wrapString);
72+
73+
// we need to set handle and callback to null,
74+
// so that those fields are created and functions
75+
// do not become megamorphic
76+
// Fields:
77+
// - oncomplete
78+
// - callback
79+
// - handle
80+
sw->InstanceTemplate()->Set(
81+
FIXED_ONE_BYTE_STRING(env->isolate(), "oncomplete"),
82+
v8::Null(env->isolate()));
83+
sw->InstanceTemplate()->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "callback"),
84+
v8::Null(env->isolate()));
85+
sw->InstanceTemplate()->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "handle"),
86+
v8::Null(env->isolate()));
87+
7188
sw->Inherit(AsyncWrap::GetConstructorTemplate(env));
89+
7290
target->Set(env->context(),
7391
wrapString,
7492
sw->GetFunction(env->context()).ToLocalChecked()).FromJust();

0 commit comments

Comments
 (0)