Skip to content

transport: presize header slice for outgoing metadata#9243

Open
geekswaroop wants to merge 1 commit into
grpc:masterfrom
geekswaroop:transport-presize-metadata-headers
Open

transport: presize header slice for outgoing metadata#9243
geekswaroop wants to merge 1 commit into
grpc:masterfrom
geekswaroop:transport-presize-metadata-headers

Conversation

@geekswaroop

@geekswaroop geekswaroop commented Jul 16, 2026

Copy link
Copy Markdown

createHeaderFields pre-sizes the headerFields slice, but the estimate omits the outgoing metadata (md and added from the outgoing context, plus t.md). For RPCs that propagate context headers (tracing, tenancy, routing, etc.), the slice starts at capacity ~8 and grows once or twice via append on nearly every call.

This change fetches the raw outgoing metadata once, adds its field count to hfLen, and reuses the fetched maps in the append loop below. Reserved headers are dropped when writing, so this may slightly over-count, which is preferable to growing the slice.

RELEASE NOTES: none

@linux-foundation-easycla

linux-foundation-easycla Bot commented Jul 16, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: geekswaroop / name: Krishna Swaroop (4b4a1ac)

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.33%. Comparing base (7d87045) to head (6e72630).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9243      +/-   ##
==========================================
+ Coverage   83.27%   83.33%   +0.06%     
==========================================
  Files         421      421              
  Lines       34084    34092       +8     
==========================================
+ Hits        28382    28411      +29     
+ Misses       4273     4254      -19     
+ Partials     1429     1427       -2     
Files with missing lines Coverage Δ
internal/transport/http2_client.go 92.57% <100.00%> (+0.24%) ⬆️

... and 24 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@easwars

easwars commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

@geekswaroop
Please sign the CLA so that we can go forward with the review. Thanks.

Comment thread internal/transport/http2_client_header_bench_test.go Outdated
@easwars easwars added Type: Performance Performance improvements (CPU, network, memory, etc) Area: Transport Includes HTTP/2 client/server and HTTP server handler transports and advanced transport features. Status: Requires Reporter Clarification labels Jul 16, 2026
@easwars easwars added this to the 1.84 Release milestone Jul 16, 2026
@easwars

easwars commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request optimizes the createHeaderFields method in http2_client.go by pre-calculating the required slice capacity for metadata headers to minimize allocations. It also introduces a new benchmark file, http2_client_header_bench_test.go, to evaluate the performance impact. The reviewer suggested adding a benchmark case that utilizes AppendToOutgoingContext to ensure full coverage of the optimization logic, providing a code suggestion to facilitate this improvement.

Comment thread internal/transport/http2_client_header_bench_test.go
createHeaderFields pre-sizes the headerFields slice, but the estimate
omits the outgoing metadata (md and added from the outgoing context,
plus t.md). For RPCs that propagate context headers (tracing, tenancy,
routing, etc.), the slice starts at capacity ~8 and grows once or twice
via append on nearly every call.

Fetch the raw outgoing metadata once, add its field count to hfLen, and
reuse the fetched maps in the append loop below. Reserved headers are
dropped when writing, so this may slightly over-count, which is
preferable to growing the slice.

This completes the TODO(mmukhi) noted above the size estimate, which
grpc#8547 addressed for scalar fields (deadline, compressors, retry
attempts) but left open for metadata. The standard unary benchmark used
in grpc#8547 sends no application metadata, so the metadata realloc path
was not visible in the profiles that motivated that PR.

Benchmark (internal/transport, BenchmarkCreateHeaderFields,
table-driven by outgoing metadata field count, benchstat over 10 runs):

  mdCount   allocs/op         B/op                sec/op
   0         5 -> 5          416 -> 416     ~ (indistinguishable)
   4         6 -> 5         1120 -> 576    -49% B, -17% allocs, -24% time
  12         7 -> 5         2528 -> 992    -61% B, -29% allocs, -38% time

Macro benchmark (benchmark/benchmain, 5 alternated 60s unary runs,
benchstat): every metric indistinguishable (all p>0.2). Expected -- the
standard workload sends no outgoing metadata, so the counting loops
iterate zero times and there is nothing to save. Confirms no regression
on the workload grpc#8547 measured.

RELEASE NOTES:
* transport: Include outgoing metadata in the header slice size
  estimate to eliminate reallocations for RPCs that propagate context
  headers.

Signed-off-by: Krishna Swaroop <krishna.swaroop@uber.com>
@geekswaroop
geekswaroop force-pushed the transport-presize-metadata-headers branch from 4b4a1ac to 6e72630 Compare July 17, 2026 09:32
@easwars

easwars commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Assigning to @mbissa for second review

@easwars

easwars commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Storing benchmark results from original PR description here for posterity:

Completes the TODO(mmukhi) above the size estimate, which #8547 addressed for scalar fields (deadline, compressors, retry attempts) but left open for metadata. The standard unary benchmark used in #8547 sends no application metadata, so the metadata realloc path was not visible in the profiles that motivated that PR.

Testing

  • ./scripts/vet.sh: pass on all checks that touch the changed code.
  • go test -cpu 1,4 -timeout 7m ./internal/transport/...: pass (24.8s).
  • go test -race -cpu 1,4 -timeout 7m ./internal/transport/...: pass (30.7s).

Micro-benchmark

BenchmarkCreateHeaderFields (added, table-driven by outgoing metadata field
count, 10 samples, benchstat):

mdCount allocs/op B/op sec/op
0 5 → 5 416 → 416 ~ (indistinguishable)
4 6 → 5 (−16.67%) 1120 → 576 (−48.57%) −4.21% (p=0.000)
12 7 → 5 (−28.57%) 2528 → 992 (−60.76%) −17.04% (p=0.000)

Macro benchmark

benchmark/benchmain unary, -benchtime=60s -workloads=unary -compression=off -maxConcurrentCalls=120 -trace=off -reqSizeBytes=1024 -respSizeBytes=1024 -networkMode=Local -recvBufferPool=simple, 5 alternated runs per side,
benchstat:

metric before after p-value
sec/op 1.824ms 1.813ms 0.548
B/op 12.98Ki 12.98Ki 1.000
allocs/op 133.7 133.6 0.286
totalOps 3.940M 3.964M 0.548
p50 latency 1.593ms 1.581ms 0.222
p90 latency 2.998ms 2.968ms 0.548
p99 latency 4.044ms 4.054ms 0.690

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

Labels

Area: Transport Includes HTTP/2 client/server and HTTP server handler transports and advanced transport features. Type: Performance Performance improvements (CPU, network, memory, etc)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants