Add gRPC codec round-trip benchmark for protobuf v2 encoding path - #2515
Open
kshitijsuri90 wants to merge 1 commit into
Open
Add gRPC codec round-trip benchmark for protobuf v2 encoding path#2515kshitijsuri90 wants to merge 1 commit into
kshitijsuri90 wants to merge 1 commit into
Conversation
Copy of encoding/protobuf/codec_bench_test.go adapted for the v2 (google.golang.org/protobuf) package. Benchmark results: BenchmarkGRPCCodec_RoundTrip/Small_350B-96 66325 47290 ns/op 23367 B/op 316 allocs/op BenchmarkGRPCCodec_RoundTrip/Medium_10KB-96 41518 86748 ns/op 108416 B/op 323 allocs/op BenchmarkGRPCCodec_RoundTrip/Large_1MB-96 1154 3120548 ns/op 15391705 B/op 541 allocs/op Co-authored-by: Cursor <cursoragent@cursor.com>
kshitijsuri90
added a commit
to kshitijsuri90/yarpc-go
that referenced
this pull request
Jul 5, 2026
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 yarpc#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>
kshitijsuri90
added a commit
to kshitijsuri90/yarpc-go
that referenced
this pull request
Jul 5, 2026
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 yarpc#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>
rabbbit
reviewed
Jul 13, 2026
| b.ResetTimer() | ||
| b.ReportAllocs() | ||
|
|
||
| for i := 0; i < b.N; i++ { |
rabbbit
approved these changes
Jul 13, 2026
rabbbit
reviewed
Jul 13, 2026
| } | ||
| defer serverDispatcher.Stop() | ||
|
|
||
| serverAddr := listener.Addr().String() |
kshitijsuri90
added a commit
to kshitijsuri90/yarpc-go
that referenced
this pull request
Jul 27, 2026
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 yarpc#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. Lint fixes (2026-07-27): gofmt field alignment in encoding/protobuf/marshal_test.go; removed the now-unused readCloser type from transport/grpc/stream.go (staticcheck U1000) — its only usage was replaced by bytesBody. RELEASE NOTES: N/A (internal optimization, no API changes) Made-with: Cursor Co-authored-by: Cursor <cursoragent@cursor.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
google.golang.org/protobuf) encoding pathencoding/protobuf/codec_bench_test.goadapted for the v2 package atencoding/protobuf/v2/Benchmark results
Test plan
go test -bench=BenchmarkGRPCCodec_RoundTrip -benchmem ./encoding/protobuf/v2/passesgo build ./encoding/protobuf/v2/compiles cleanlygo vet ./encoding/protobuf/v2/reports no issuesMade with Cursor