Repository navigation
perf: round-2 optimizations — 22 of 27 backlog items resolved - #28
Merged
Merged
Conversation
O19/O20 (static): canonical serve root cached in OnceCell instead of canonicalizing per request; select_file now receives the metadata that handle already collected, removing a duplicate stat. Static requests drop from up to five blocking-pool hops to ~3. O11 (request-id): one HeaderValue built and cloned (refcount bump) instead of cloning the id string plus two parse/validate/alloc cycles. O12 (compression): encoding negotiation moved before inner.call using the borrowed header; absent/empty Accept-Encoding short-circuits to pass-through with zero allocations; preferred_encoding iterates a fixed candidate list with no Vec churn; accept::negotiate rewritten allocation-free (eq_ignore_ascii_case over the raw slice). O13 (trace): when make_span gated the span, the response tail skipped latency formatting and all record/event calls (previously an unconditional String alloc per request). Span fields borrow method/path and use a &'static str version table. O3/O8 (router): MethodRouter.layers moved behind Arc (clone is now allocation-free) and per-handler layer stacks are folded exactly once after with_state binds state — nested routes with sub-router middleware no longer rebuild N boxed services per request. Unbound generic routers keep the legacy lazy path; layer/handler mutations invalidate the bake. Co-Authored-By: Claude <noreply@anthropic.com>
…rrow Round-2 follow-ups: O4, O5, O6, O9, O27. O5 — path-param keys are interned on a route's first match (OnceLock on the route entry) so later requests only refcount instead of copying each name via Arc::from; values still copy only when decoding changed bytes. O4 — 404/fallback sentinel labels (__fallback/__unmatched) are LazyLock<Arc<str>> statics instead of a fresh heap allocation per scanned request. O6 — StripPrefixService no longer rebuilds a byte-identical Uri (alloc + parse) when the prefix is "/" or does not apply. O9 — ResponseBuilder::text_static for framework-generated bodies (404/ 405): zero-copy body bytes and Content-Type from 'static data. O27 — MethodRouter::call borrows the request method instead of cloning. Co-Authored-By: Claude <noreply@anthropic.com>
Round-2 follow-ups: O16 + O9 completion. - DefaultBodyLimitService: was Box::pin(inner.call(req)) — a double box of an already concrete future. Now type Future = S::Future, zero allocation. - RequestBodyLimitService: happy path returns the inner future directly via a two-variant enum future (Accept = S::Future, Reject = a ready 413). Only the rare over-limit rejection allocates. - SetRequestHeaderService / SensitiveHeadersService: synchronous request mutation followed by pure delegation — same treatment. - text_static used for the framework's own 404/405 bodies. Co-Authored-By: Claude <noreply@anthropic.com>
O2 — ErasedHandler::call now takes &self (concrete handler cloned into the returned future, H: Clone is already a supertrait). The dispatch fast path calls the stored handler directly, removing one boxed allocation per request on every route. O13/O22/O23 — observe middlewares: latency formatting and span records skipped entirely when the span was gated off; route label skipped when irrelevant; request-id HeaderValue built once per request instead of twice; OTel no longer sets the same attributes tracing-opentelemetry already converts from recorded fields. O24 — to_bytes_limited collects frame handles and returns a single frame zero-copy; multi-frame bodies concatenate once into a right-sized buffer. O26 — JSON/Form content-type fast paths skip the full Mime parse for exact constants; SSE data/comment normalization skips two allocations when no CR is present. O1 — investigated: StateBound's state clone feeds an argument the bound handler ignores, but eliminating it requires the breaking Handler-trait state-by-ref migration. Deferred with the Arc<AppState> idiom documented at the call site. Full gates on rustc 1.98: clippy --all-features -D warnings clean, fmt clean, test --workspace --all-features 171 passed / 0 failed. Co-Authored-By: Claude <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
Second optimization pass over the post-audit codebase, working through the
AUDIT_FINDINGS.mdRound-2 backlog (O1–O27) identified after the firstbenchmark report. 22 of 27 items resolved; the rest are deferred with
reasons documented in the tracker.
Highlights
Core dispatch
ErasedHandler::call(&self)— dispatch fast path no longer allocates aboxed handler per request (O2)
with_state— nested routeswith middleware no longer rebuild N boxed services per request (O3)
MethodRouter.layersbehind anArc— router clone is allocation-free (O8)labels become
LazyLockstatics (O5, O4)Middleware
absent/empty Accept-Encoding = zero work; allocation-free negotiator and
fixed candidate lists (O12, O25)
off; span fields borrow instead of allocating; static version table (O13)
clone (O11); CORS derives its constant header values at construction —
allocation-free preflights (O15)
including a two-variant future for the body-limit rejection path
Static / extractors / observe
removed — up to 3 fewer blocking-pool hops per file (O19, O20)
stack-buffer status labels (O21)
HeaderValue built once; duplicate OTel set_attribute calls removed (O22, O23)
no CR present (O26)
Deferred (documented in tracker)
clone at StateBound remains until then (Arc idiom documented at
the call site)
Verification
Full gates on rustc 1.98.0 (same as CI):
Re-run the benchmark suite after merging to refresh BENCHMARK_REPORT.md.