Skip to content

quic: apply multiple quic performance and packet handling improvements - #63267

Closed
jasnell wants to merge 28 commits into
nodejs:mainfrom
jasnell:jasnell/quic-backend-improvements
Closed

quic: apply multiple quic performance and packet handling improvements#63267
jasnell wants to merge 28 commits into
nodejs:mainfrom
jasnell:jasnell/quic-backend-improvements

Conversation

@jasnell

Copy link
Copy Markdown
Member

A number of planned backend changes and improvements in the packet handling. Yields a net performance improvement of around 10% higher rps. Sets us up to be able to better leverage libuv/libuv#5116 if/when that lands (@santigimeno@saghul fyi) which will provide an even larger perf boost. Fixes a bug while we're at it.

@nodejs/quic

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/quic

@jasnell
jasnell requested review from Qard and pimterryMay 12, 2026 18:52
@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels May 12, 2026
@jasnelljasnell changed the title meta: add additional gitignore entriesquic: apply multiple quic performance and packet handling improvementsMay 12, 2026
@nodejs-github-bot

This comment was marked as outdated.

@codecov

This comment was marked as outdated.

@nodejs-github-bot

This comment was marked as outdated.

@jasnell
jasnellforce-pushed the jasnell/quic-backend-improvements branch from e921c66 to ef9e8daCompareMay 13, 2026 03:54
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

This comment was marked as outdated.

@jasnell
jasnell requested a review from mcollinaMay 14, 2026 13:37
@jasnelljasnell added the experimental Issues and PRs related to experimental features. label May 14, 2026
@jasnell
jasnellforce-pushed the jasnell/quic-backend-improvements branch 4 times, most recently from bbe613a to 6ef4e68CompareMay 15, 2026 13:41
@Ethan-Arrowood

Copy link
Copy Markdown
Contributor

This diff is quite large. Can you provide a short summary of the most relevant parts? Such as maybe notable performance improvement areas, the bug fix, and whatever else seems relevant. That'll help expedite reviews, thanks!

@jasnell
jasnellforce-pushed the jasnell/quic-backend-improvements branch 2 times, most recently from bfc3adc to c9c714eCompareMay 15, 2026 16:56
@jasnell

jasnell commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Just an idea of where we're at... running a local 'hello world' benchmark using h2load as the client driver on the same 88 core machine... 4 runs with 100,000 requests over 100 concurrent connections:

image

The rps is not as high as I'd like and http/1 still beats it by a long way but it's a solid result for current state.

The primary performance bottleneck right now is the JS side, GC, and the C++/JS boundary cross. We lose a fair amount of throughput in the context switching.

My current goal is to get the per-request down to 10 microseconds.

@jasnell

jasnell commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Ok, with a bit of perf tunning and mangling with both h2load options and v8 perf tuning options on the server, with a node binary built with pointer compression enabled... we can get ~101k-102k rps. Still slower than http/1, on par with http/2 when pointer compression is enabled. Overall a great result given that I've only just started the perf tuning of the implementation.

/tmp/opencode/local/bin/h2load --h3 -n 200000 -c 5 -w 26 -W 30 -m 10 -D 2 https://127.0.0.1:4433
starting benchmark...
spawning thread #0: 5 total client(s). Timing-based test with 0s of warm-up time and 2s of main duration for measurements.
Warm-up started for thread #0.
progress: 20% of clients started
progress: 40% of clients started
progress: 60% of clients started
progress: 80% of clients started
progress: 100% of clients started
Warm-up phase is over for thread #0.
Main benchmark duration is started for thread #0.
TLS Protocol: TLSv1.3
Cipher: TLS_AES_128_GCM_SHA256
Certificate: ECDSA P-256 256 bits
Negotiated Group: X25519
Resumption: no
Application protocol: h3
Main benchmark duration is over for thread #0. Stopping all clients.
Stopped all clients for thread #0
finished in 2.02s, 102130.00 req/s, 2.23MB/s
requests: 204260 total, 204310 started, 204260 done, 204260 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 204260 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 4.46MB (4676960) total, 797.97KB (817120) headers (space savings 87.50%), 2.14MB (2247080) data
UDP datagram: 33273 sent, 20462 received
min max median p95 p99 mean sd +/- sd
request : 150us 53.98ms 435us 490us 813us 487us 1.28ms 99.59%
connect : 4.89ms 5.64ms 5.30ms 5.64ms 5.64ms 5.28ms 295us 60.00%
TTFB : 8.11ms 8.25ms 8.18ms 8.25ms 8.25ms 8.18ms 54us 60.00%
req/s : 20402.15 20442.34 20417.92 20442.34 20442.34 20421.97 17.40 60.00%
min RTT : 22us 115us 38us 115us 115us 57us 40us 80.00%
smoothed RTT: 86us 226us 164us 226us 226us 163us 60us 60.00%
packets sent: 6219 7436 6373 7436 7436 6656.60 545.25 80.00%
packets recv: 4092 4099 4092 4099 4099 4094.40 3.36 80.00%
packets lost: 0 0 0 0 0 0.00 0.00 100.00%
GRO packets : 1 1 1 1 1 1.00 0.00 100.00%

jasnell added 5 commits May 15, 2026 16:08
Signed-off-by: James M Snell <jasnell@gmail.com>
Use a uv_check_t on BindingData to process outbound
pending packet send, and use TrySend for actually sending
packets when possible. Results in an 8% improvement in
req/s and ~24% improvement in p95 latency.
Also sets us up better for future improvements in
libuv if the changes proposed in libuv/libuv#5116
are accepted.
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
Signed-off-by: James M Snell <jasnell@gmail.com>
Improves overall performance and sets us up for eventual
support for GRO/GSO batching in libuv in the future.
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
Set up for when libuv eventually supports ECN marking.
Pass the ECN marking stuff into ngtcp2.
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
@jasnell

Copy link
Copy Markdown
MemberAuthor

Landed in f835cc4...2f56cd2

@jasnelljasnell closed this May 21, 2026
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Use a uv_check_t on BindingData to process outbound
pending packet send, and use TrySend for actually sending
packets when possible. Results in an 8% improvement in
req/s and ~24% improvement in p95 latency.
Also sets us up better for future improvements in
libuv if the changes proposed in libuv/libuv#5116
are accepted.
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Improves overall performance and sets us up for eventual
support for GRO/GSO batching in libuv in the future.
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Set up for when libuv eventually supports ECN marking.
Pass the ECN marking stuff into ngtcp2.
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Every stream, session, and endpoint creates aliased
structs for stats and state. These were creating
v8::ArrayBuffer allocations and views for each instance,
which is expensive. This adds a new arena mechanism
for AliasedStructs that allocates in pages and allows
Streams and Sessions to share the same underlying
ArrayBuffer for their stats and state. Since these
are never exposed to users, this is safe and results
in a significant reduction in allocation counts.
Each arena maintains a freelist of pages, where each
page is a max of 16KB bytes. Pages are lazily allocated
and freed as needed. Each slot in the arena corresponds
to a single struct instance, and the slot index is
used to calculate the byte offset within the page for
that struct's view.
The perf improvement is modest but measurable. The
key benefit is in reduced memory fragmentation and GC
overhead.
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
* Adds invalid this protections in the stats classes
* Collects internal private fields into a single private symbol
to reduce the number of private field accesses
* Adds and corrects isQuic* class checks
* Preserve stream id and direction after destroy
* Improve stats close snapshotting
* Fixes a handful of other bugs and doc issues
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
aduh95 pushed a commit that referenced this pull request May 22, 2026
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: #63267
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
@martenrichter

Copy link
Copy Markdown
Contributor

@jasnell: Can I ask something about this PR and the performance optimization?
As I understood it, it should coalesce incoming data.
I am currently running on my webtransport branch a test against a quiche wt server. There are of course some modifications, but it is basically the same code path that every serverside h3 data stream data enters (ReceiveData) in the client.
The test sends many 1024 bytes over the line and ReceiveData gets them from the parser in100 bytes packages, which are passed down to the js stream. The performance is so poor that my test fails on timeout. (It is actually this one https://github.com/fails-components/webtransport/blob/08f95586a949463c8b2f502cde59f166deeee996/test/bidirectional-streams.spec.js#L131, but I see the 100 bytes also at other tests).
So I guess the coalescing does not work for the http3 route.

I do not know if it is the parser of nghttp3 that changes the calling pattern, or it is something else.
(I do not have a generic test yet, I have just discovered it here, and I thought before opening an issue, I would ask here as it is related.

@martenrichter

Copy link
Copy Markdown
Contributor

I found the underlying cause. I have drafted an issue #64035

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author readyPRs that have at least one approval, no pending requests for changes, and a CI started.c++Issues and PRs that require attention from people who are familiar with C++.experimentalIssues and PRs related to experimental features.lib / srcIssues and PRs related to general changes in the lib or src directory.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jasnell@nodejs-github-bot@Ethan-Arrowood@martenrichter@mcollina