perf(http): allocate response headers lazily - #14
Merged
Conversation
Amp-Thread-ID: https://ampcode.com/threads/T-01a01745-eab4-70fc-a557-1120d96883f9 Co-authored-by: Upd4ting <upd4ting@gmail.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a01745-eab4-70fc-a557-1120d96883f9 Co-authored-by: Upd4ting <upd4ting@gmail.com>
7 tasks
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.
🔗 Linked issue
None.
❓ Type of change
📚 Description
HTTPResultpreviously allocated an empty headers object for every result, including the common case where no custom header is ever added or observed. This change creates that object only whenaddHeader()or mutablegetHeaders()access needs it.removeHeader()remains a no-op when no store exists. The newpeekHeaders()API returns the current store as readonly, orundefined, so HTTP implementations can inspect headers without creating it.The response serialization and emission paths are intentionally unchanged. In particular, object bodies are still serialized immediately,
getBody()still returns the serialized string, mutable headers retain stable identity once observed, and status/content type/HEAD/stream/error behavior is preserved. No API middleware fast path or benchmark-specific branch is added.Profiling
A retained-allocation profile creates 500,000 small JSON results per run, alternates before/after order, forces GC before measurement, and repeats 7 times on Node 22.19.0:
This removes 56 retained bytes per headerless result in this profile and improves isolated construction throughput by 21.3%.
Header inspection microbenchmark
A second retained-allocation benchmark models an HTTP implementation inspecting headers on 500,000 small JSON results. It alternates
getHeaders()(before) andpeekHeaders()(after), forces GC, and repeats 7 times:getHeaders()peekHeaders()For headerless responses, non-allocating inspection saves 56 retained bytes/result and improves this isolated path by 22.3%. Once headers exist,
peekHeaders()exposes the same store as readonly andgetHeaders()remains mutable and identity-stable.HTTP benchmark
Method: one Node process exposing matched before/after
HTTPResultroutes; the before route restores the eager empty headers allocation.autocannon, 50 connections, 3 s warm-up per variant/scenario, 7 alternating 3 s repetitions. Values are mean req/s ± sample standard deviation; p99 is the mean reported p99. Errors include connection errors, timeouts, and non-2xx responses.The full HTTP differences are smaller than run-to-run dispersion, so this PR does not claim a statistically clear req/s improvement. Its demonstrated benefit is the removed per-result allocation. Large JSON remains dominated by
JSON.stringify; changing that would alter the public immediate-serialization contract.Small JSON framework reference
A separate rotating-order run used the same machine, payload,
autocannonsettings, warm-up, and 7 repetitions:HTTPResultowner harnessThe owner harness exceeds both references, but it is not a full Antelope application and must not be compared directly with the external ~22.1k GET / ~15.2k POST Antelope figures. End-to-end gains beyond this allocation require work in the API implementation repository, which is deliberately outside this PR.
Validation
pnpm run lintpnpm run buildpnpm dlx @antelopejs/core module test .— 73 passing📝 Checklist