Small unary RPC fixed cost: tonic unary vs raw h2 gRPC-like loopback path
Summary
While investigating high-rate 1KiB metadata-style internode RPCs in RustFS, we isolated a small unary RPC benchmark to separate application wrapper cost from tonic/h2 fixed cost.
The repro compares three loopback lanes:
tonic_unary: generated tonic unary client/server over HTTP/2.
tonic_unary_boxed_tower_wrapper: the same tonic unary path with a minimal boxed tower service wrapper around the client transport.
raw_h2_grpc_like_prefix_headers_trailers: direct h2 client/server with gRPC-like request/response headers, a 5-byte gRPC message prefix, prost encode/decode, and grpc-status: 0 response trailers.
The raw h2 lane is not production-equivalent to tonic. It is intended as a narrow fixed-cost probe for high-rate small unary calls.
Versions Tested
- tonic
0.14.6
- h2
0.4.18
- prost
0.14.4
- tokio
1.53.1
- payload: 1024 bytes
- measured calls per lane: 20,000 after 1,000 warmup calls
- environment: macOS arm64 local loopback
Results
Three consecutive local release runs:
tonic_unary: calls=20000 payload=1024B avg=99799ns p50=97750ns p90=109334ns p99=122417ns
tonic_unary_boxed_tower_wrapper: calls=20000 payload=1024B avg=99406ns p50=97458ns p90=108917ns p99=121333ns
raw_h2_grpc_like_prefix_headers_trailers: calls=20000 payload=1024B avg=74503ns p50=73708ns p90=85792ns p99=99416ns
tonic_unary: calls=20000 payload=1024B avg=101505ns p50=99458ns p90=112042ns p99=125542ns
tonic_unary_boxed_tower_wrapper: calls=20000 payload=1024B avg=99951ns p50=98125ns p90=109292ns p99=121667ns
raw_h2_grpc_like_prefix_headers_trailers: calls=20000 payload=1024B avg=73356ns p50=71250ns p90=84583ns p99=96416ns
tonic_unary: calls=20000 payload=1024B avg=101963ns p50=98709ns p90=111958ns p99=152166ns
tonic_unary_boxed_tower_wrapper: calls=20000 payload=1024B avg=100227ns p50=97875ns p90=109875ns p99=124209ns
raw_h2_grpc_like_prefix_headers_trailers: calls=20000 payload=1024B avg=74070ns p50=72125ns p90=85709ns p99=99417ns
In this repro, the minimal boxed tower wrapper does not add measurable overhead. The repeatable gap is about 25-28 microseconds per 1KiB unary call between generated tonic unary and direct h2 with gRPC-like framing/trailers plus prost encode/decode.
Question
Is there a known lighter unary path, configuration, or avoidable allocation/readiness/body abstraction cost in tonic Grpc::unary for high-rate small unary RPCs?
If the gap is mostly required by full gRPC/tonic semantics, that would also be useful confirmation. For RustFS, it would push the optimization direction toward reducing per-object RPC count at the application fanout layer rather than tuning h2 windows/pooling or micro-optimizing caller-side fixed headers.
Reproducer
Small unary RPC fixed cost: tonic unary vs raw h2 gRPC-like loopback path
Summary
While investigating high-rate 1KiB metadata-style internode RPCs in RustFS, we isolated a small unary RPC benchmark to separate application wrapper cost from tonic/h2 fixed cost.
The repro compares three loopback lanes:
tonic_unary: generated tonic unary client/server over HTTP/2.tonic_unary_boxed_tower_wrapper: the same tonic unary path with a minimal boxed tower service wrapper around the client transport.raw_h2_grpc_like_prefix_headers_trailers: directh2client/server with gRPC-like request/response headers, a 5-byte gRPC message prefix, prost encode/decode, andgrpc-status: 0response trailers.The raw h2 lane is not production-equivalent to tonic. It is intended as a narrow fixed-cost probe for high-rate small unary calls.
Versions Tested
0.14.60.4.180.14.41.53.1Results
Three consecutive local release runs:
In this repro, the minimal boxed tower wrapper does not add measurable overhead. The repeatable gap is about 25-28 microseconds per 1KiB unary call between generated tonic unary and direct h2 with gRPC-like framing/trailers plus prost encode/decode.
Question
Is there a known lighter unary path, configuration, or avoidable allocation/readiness/body abstraction cost in tonic
Grpc::unaryfor high-rate small unary RPCs?If the gap is mostly required by full gRPC/tonic semantics, that would also be useful confirmation. For RustFS, it would push the optimization direction toward reducing per-object RPC count at the application fanout layer rather than tuning h2 windows/pooling or micro-optimizing caller-side fixed headers.
Reproducer