http: reduce per-request server overhead#64348
Open
anonrig wants to merge 2 commits into
Open
Conversation
Cache the lenient-header-validation decision per message instead of re-deriving it through the six-branch req/socket/server walk on every setHeader/appendHeader call (and per addTrailers key); the inputs are fixed once the message is constructed. Pre-filter the known-field matcher on (length, first character) so ordinary headers skip the per-header toLowerCase() allocation; the filter only rejects names that cannot match. Load the `socket` prototype accessor once per body write instead of three times, hoist the repeated _header/_keepAliveTimeout/ _maxRequestsPerSocket/_contentLength/headers.length loads the engine cannot fold across calls, and flush corked chunked buffers through a shared callback runner instead of allocating a closure per flush. The hoists mirror what Bun's fork of these files carries on top of the shared lineage (bun/src/js/node/_http_outgoing.ts: write_ msgSocket, _send header local, _storeHeader/processHeader length locals, runChunkCallbacks, flat one-shot setHeader validation). Mechanism benchmarks driving the shipped classes through the public API (fresh process per sample, 30 interleaved samples per binary, Welch t-test): flushHeaders with five headers +8.25% (p=6.2e-11), a 24-header response +4.26% (p=6.7e-7), tight setHeader loop +2.64% (p=2.0e-3); sign-stable in an independent 15-sample repeat. No statistically significant regression across the captured benchmark/http suite (headers, incoming_headers, chunked, end-vs-write-end, client-request-body, create-clientrequest, check_*). The new test locks the matcher semantics: known fields keep being recognized in any casing, and unknown names sharing a known field's length and first letter are emitted verbatim. Refs: https://github.com/oven-sh/bun/blob/main/src/js/node/_http_outgoing.ts Assisted-by: Grok Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Eliminate per-request work that is invariant or redundant on the server response path: - end(chunk) issued a second, empty socket.write whose only purpose was to carry the finish callback, paying a full Writable pass and an extra cork-queue entry per response. When the header block is already rendered and the framing is final (no chunked trailer, no strict content-length accounting), the finish callback rides the body write itself; writes complete in order, so the observable finish timing is unchanged. - The response options bag and the pending-data callback only depend on the connection, not the request: allocate them once per socket and reuse them for every response on a keep-alive connection. - The response close listener is a no-op unless a response is attached (socket._httpMessage guard), so it is installed once per socket instead of paying the add/removeListener pair per response. - Status lines for default reason phrases are cached per status code, and the Keep-Alive header line is memoized per (timeout, max) configuration. Measured with a CPU-per-request harness (fresh process per sample, 25x600k-request interleaved samples, Welch t-test) on a keep-alive hello-world server: +7.34% requests per CPU-second (p=2.0e-14), and about +6.4% requests/sec under autocannon. All test/parallel http and https tests pass. Assisted-by: Grok Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Collaborator
|
Review requested:
|
jasnell
reviewed
Jul 8, 2026
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #64348 +/- ##
==========================================
+ Coverage 90.23% 90.25% +0.01%
==========================================
Files 741 741
Lines 240979 241095 +116
Branches 45401 45417 +16
==========================================
+ Hits 217449 217594 +145
+ Misses 15112 15064 -48
- Partials 8418 8437 +19
🚀 New features to boost your workflow:
|
af61c79 to
43a9d71
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Server-side fast-path work reducing per-request overhead on the hot
keep-alive path. Builds on #64346 (its commit is the base of this branch and
appears in the diff until it lands; the two commits on top are this PR).
What changes
http: reduce per-request server overheadend(chunk)issued a second, emptysocket.writewhose only purpose wasto carry the finish callback — a full Writable pass and an extra
cork-queue entry per response. When the header block is already rendered
and the framing is final (no chunked trailer, no strict content-length
accounting), the finish callback now rides the body write; writes
complete in order, so observable finish timing is unchanged.
connection: allocated once per socket, reused per response.
already guarded on
socket._httpMessage), instead of anadd/removeListener pair per response.
Keep-Alive:line is memoized per (timeout, max) configuration.—http: move invariant IncomingMessage defaults to the prototypedropped from this PR after review (semver-major per @jasnell); will be
resubmitted separately with the proper label.
Measurements
CPU-per-request harness (fresh server process per sample, in-process
pipelined keep-alive clients, interleaved old/new, Welch t-test), hello
world with Content-Length, baseline = #64346's head:
Cross-check under autocannon (
-c 50): 130.5K -> 138.9K req/s (+6.4%)for the first commit alone. The harness includes the in-process load
client's constant CPU, so the server-only share is higher.
Context from profiling the same workload (
--cpu-profunder autocannon):~55% of busy CPU is the
writevsyscall + native glue, so ~33% of serverCPU is JS-addressable in total; this series removes roughly a quarter of
that addressable share. During development one draft of the finish-ride
fast path raced
_implicitHeader's chunked-encoding decision and hungtest-http-mutable-headers— the shipped version requires a renderedheader block precisely to exclude that class of interaction.
Correctness
test/parallel/test-http-*,test-http.js,test-https-*, andtest-http2-*pass; linters clean.optimizeEmptyRequests(documentedas breaking for servers that read bodies of body-header-less requests —
measured at an additional +4.4% for opt-in users) and any change to
drain/backpressure semantics.AI assistance disclosure
This is an AI-assisted pull request: profiling, implementation, the
measurement methodology, and this description were produced by an AI agent
(Grok, xAI) under my direction; commits carry
Assisted-by: Grok. Ireviewed and take responsibility for every change.