You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Eliminate redundant receive-side copy in gRPC protobuf decode path
Every protobuf message received over gRPC was copied twice: once by
the codec (Materialize, necessary) and again by the encoding layer
(bufferpool.ReadFrom, redundant). The second copy exists because the
transport wraps the already-materialized []byte in bytes.NewReader,
and the encoding layer has no way to extract the raw bytes.
Add a bytesBody type to the transport layer that implements
io.ReadCloser and exposes the underlying []byte via a Bytes() method.
The encoding layer's unmarshal function type-asserts for this method
and skips the bufferpool copy when available. Non-gRPC transports
(HTTP, TChannel) fall through to the existing ReadFrom path.
Changes:
- transport/grpc: Add bytesBody with Bytes() method for zero-copy access
- transport/grpc: Update all four receive paths (server stream, client
stream, unary inbound, unary outbound) to use bytesBody
- encoding/protobuf: Add Bytes() fast-path in unmarshal (gogo and v2)
Safety:
- bytesBody is unexported and implements io.ReadCloser, the interface
expected by transport.Request.Body and transport.StreamMessage.Body.
It is a drop-in replacement for bytes.NewReader / ioutil.NopCloser.
- The Bytes() fast-path is opt-in via interface type assertion. Any
reader that does not implement Bytes() (HTTP, TChannel) falls through
to the existing bufferpool.ReadFrom slow path with zero behavior change.
- The fast-path calls unmarshalBytes, the same function the slow path
calls after draining the reader — deserialization logic is identical.
- No public API changes; bytesBody is internal to transport/grpc.
Benchmark added in #2515
Benchmark (BenchmarkUnmarshalBytesReader, n=10, AMD EPYC 9B45, Go 1.26.1):
Library CPU Δ Heap Δ GC Cycles Δ GC Pause Δ
gogo -16.1% +1.1% -3.6% -9.3%
v2 -15.1% +0.9% -2.8% -7.6%
average -15.6% +1.0% -3.2% -8.5%
Heap increase at small payloads (<1KB) is due to bytesBody struct
being marginally larger than bytes.Reader; at >=10KB the eliminated
bufferpool copy dominates and heap decreases.
RELEASE NOTES: N/A (internal optimization, no API changes)
Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
0 commit comments