- Rust 100%
- README: clarify tonic runs tokio's multi-threaded work-stealing runtime (like Go's GOMAXPROCS), neither is thread-per-core - channeltest: spread load over N tonic channels — 1 conn 42.5k req/s, 8 conns 133.5k req/s; single-channel plateau is per-connection serialization, not CPU - benchmark: connection-column framing (single channel by construction) |
||
|---|---|---|
| proto/greet/v1 | ||
| src | ||
| .gitignore | ||
| build.rs | ||
| Cargo.toml | ||
| README.md | ||
rust-grpc-demo
A minimal demo of a Rust server and client communicating over
protobuf using gRPC via tonic
(the successor of github.com/grpc/grpc-rust) and prost, with codegen by
tonic-prost-build + protox (no system protoc needed).
This is the Rust counterpart of the sibling repos, all sharing the same schema, all four RPC modes, and the same benchmark methodology:
pyrpc-demo— Connect RPC + protobuf-py (Python)pygrpc-demo— gRPC + classic protobuf (Python)- this repo — gRPC + tonic/prost (Rust)
golang-grpc-demo— gRPC + protobuf-go (Go)
Project layout
proto/greet/v1/greet.proto # the schema: greet.v1.GreetService (4 RPC modes)
build.rs # codegen: protox parses, tonic_prost_build emits
src/server.rs # gRPC server (tonic + tokio)
src/client.rs # gRPC client (tonic)
src/benchmark.rs # performance benchmark (codec + RPC throughput)
Dependencies (Cargo.toml)
| Dependency | Why it's here |
|---|---|
tonic |
gRPC runtime for Rust (HTTP/2 via hyper, tokio-based). |
tonic-prost |
Codec connecting tonic to prost messages (used by generated code). |
prost |
Protobuf message derive and wire format. |
tokio |
Async runtime for server/client/benchmark. |
tokio-stream |
Stream adapters for streaming RPCs. |
futures |
Stream combinators (client demos). |
tonic-prost-build (build) |
Generates service stubs from the .proto. |
protox (build) |
Pure-Rust protoc replacement — parses .proto to a FileDescriptorSet so no system protoc is required. |
Build & run
cargo build --release
cargo run --release --bin server # terminal 1
cargo run --release --bin client # terminal 2
Expected client output (all four RPC kinds):
--- unary: Greet ---
[client] Hello, world!
--- server streaming: GreetStream ---
[client] Hello #1, streamer!
[client] Hello #2, streamer!
[client] Hello #3, streamer!
--- client streaming: GreetCount ---
[client] server counted 3 requests
--- bidirectional streaming: GreetChat ---
[client] Hi, alice!
[client] Hi, bob!
[client] Hi, carol!
Threading model
Both this repo and the Go sibling are multi-threaded out of the box — neither is thread-per-core:
- tonic (this repo) runs on tokio's multi-threaded work-stealing
scheduler (
rt-multi-thread;#[tokio::main]defaults to it, workers =available_parallelism(), 10 on the benchmark machine). Requests are futures, not threads. - grpc-go schedules goroutines across
GOMAXPROCSOS threads (defaults to core count).
So why does the single-channel benchmark plateau at ~1.9× while Go reaches
3.7× on its single connection? Not CPU — per-connection serialization.
src/bin/channeltest.rs spreads the same load (c=64) over N channels
(each channel = one HTTP/2 connection):
| channels | req/s |
|---|---|
| 1 | 42,548 |
| 2 | 74,017 |
| 4 | 109,840 |
| 8 | 133,522 |
With 8 channels Rust is the fastest stack in the whole comparison (~134k req/s). Run it yourself:
cargo run --release --bin server # terminal 1
cargo run --release --bin channeltest # terminal 2
Benchmark
Same methodology as the sibling repos:
- Codec — in-process serialize/deserialize of
GreetRequestat 64 / 128 / 256 / 512 / 1024 / 2048 KiB payloads, each timed ≥0.5 s per direction. - Unary throughput vs concurrency — 2,000
Greetround-trips at each concurrency level (1, 4, 16, 64), with live TCP connection counts (vianetstat). - Unary throughput, large payloads — 200 round-trips, 2 MiB payloads.
- Bidi streaming throughput — 5,000 messages over one
GreetChatstream.
cargo run --release --bin server # terminal 1
cargo run --release --bin benchmark # terminal 2
See the cross-language results table in
pyrpc-demo's README.