perf(ext/http): optimize Deno.serve() hot path#32736
Open
bartlomieju wants to merge 6 commits intomainfrom
Open
perf(ext/http): optimize Deno.serve() hot path#32736bartlomieju wants to merge 6 commits intomainfrom
bartlomieju wants to merge 6 commits intomainfrom
Conversation
Key optimizations: - Add sync fast path in mapToCallback to avoid Promise creation when handler returns Response synchronously - Make Response headers lazy - defer Headers wrapper creation until actually accessed - Add fast constructor path for new Response(string) to skip webidl converters and extractBody intermediate objects - Skip ServeHandlerInfo allocation when handler doesn't use info param - Skip compression/Vary header processing when compression is None - Avoid RefCell borrow for response headers when not compressing - Add fast path in respondWith to send string bodies directly via body.source without streamOrStatic indirection - Eliminate closure allocation in fromInnerRequest by using stored guard - Share urlList array and use prototype for inner response url() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Inline serve request handling in non-tracing path to reduce function call overhead - Add Response.json() fast path that avoids extractBody intermediates - Fix asyncContextSnapshot property name (was using wrong name) - Add fast path in op_http_set_response_body_text for small bodies (<64 bytes) to skip compression check entirely - Use inner response prototype to share url() method across instances Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the serve handler has 0 parameters (e.g., () => new Response("ok")),
skip creating InnerRequest and Request objects entirely since they won't
be used. This avoids 2 object allocations per request.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add op_http_set_response_body_text_with_header that sets header + body + status in a single op dispatch (avoids separate set_header call) - Inline respondWith into the sync fast path to eliminate function call - Check bodyUsed via inner body directly instead of going through response.bodyUsed getter (avoids assertBranded + getter chain) - Access inner.type directly instead of response.type (avoids assertBranded) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mark responses created via the fast constructor path (new Response(string)) with a _fastResponse symbol. In the serve handler, detect this marker and skip all validation (type check, bodyUsed check, ResponsePrototype check) going directly to the combined op. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ructor
When Response is constructed with headers as a plain object (the most
common pattern: new Response(body, { headers: { key: value } })), add
headers directly to headerList instead of going through fillHeaders
which requires creating a Headers wrapper object.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
Summary
Optimizes the
Deno.serve()hot path by reducing per-request object allocations, eliminating unnecessary function calls, and adding fast paths for the most common response patterns.Benchmark results (wrk, 2 threads, 10 connections, 10s, macOS ARM64)
new Response("Hello, World!")new Response(JSON.stringify(...), {headers})Changes
ext/http/00_serve.ts— Serve handler hot pathmappedcallback is no longerasync. When the handler returns aResponsesynchronously (not a Promise), it processes it without creating a Promise or going through the microtask queue. Async handlers fall through tohandleAsyncResponse.new Response(string): Responses marked with_fastResponseskip all validation (type check, bodyUsed check, prototype check) and go directly to the combined Rust op.respondWithandtoInnerResponseare inlined to eliminate function call overhead and redundant property accesses.inner.body.streamOrStatic?.consumeddirectly instead of going throughresponse.bodyUsedgetter (avoidsassertBranded+ getter chain).ServeHandlerInfoallocation: Only created when handler has 2+ parameters.InnerRequest/Requestcreation for 0-arg handlers.asyncContextSnapshotproperty name: Was accessingcontext.asyncContext(undefined) instead ofcontext.asyncContextSnapshot.ext/fetch/23_response.js— Response constructornew Response(string): Inlines response creation, skippingwebidlConvertersBodyInitDomString,webidlConvertersResponseInitFast, andextractBody. Saves ~2 object allocations.new Response(string, {status, headers}): Similar fast path with init.Response.json()path: AvoidsextractBodyintermediates.Headerscreation:Headerswrapper only created when accessed, not in constructor.{ "content-type": "application/json" }added directly to headerList withoutHeaderswrapper.url()method on shared prototype.urlList: User-created responses share empty urlList.initializeAResponse.ext/fetch/23_request.js— Request constructionHeaderscreation: Same pattern as Response.fromInnerRequest: Stores guard directly instead of closure.ext/http/http_next.rs— Rust opsop_http_set_response_body_text_with_header: Sets header + body + status in one op.Vary: Accept-Encoding: Only when compression is considered.None.Test plan
Deno.serve()with sync handlersDeno.serve()with async handlersResponse.json()correctnessnew Response(body, { headers: {...} })headers correctnessnew Response(body, { status: 404 })status correctnessnew Response(null)empty body🤖 Generated with Claude Code