Back to Writing
10 min read

Rust vs Go Threading Models: A Performance Deep Dive

Rust Go Performance Threading

Rust vs Go Threading Models: A Performance Deep Dive

The debate between Go and Rust often centers around memory safety and compilation speed. However, for high-performance backend systems, the true differentiator lies in how each language handles concurrency at scale.

Threading Models Benchmark

Go’s M:N Scheduling (Goroutines)

Go’s runtime takes a highly opinionated approach with its M:N scheduler. It multiplexes M lightweight goroutines onto N OS threads.

Advantages:

  • Developer Ergonomics: The mental model is essentially synchronous. You write blocking code, and the runtime handles the complexity of parking and resuming execution transparently.
  • Preemption: Go’s scheduler is preemptive (cooperatively preemptive via function calls, and fully preemptive since Go 1.14 via signals). A tight loop won’t permanently starve the system.

Drawbacks:

  • Stack Growth: Each goroutine starts with a small stack (typically 2KB) that grows dynamically. This growth requires copying the stack and adjusting pointers, which can cause micro-stalls during high-concurrency spikes.

Rust’s Async/Await + OS Threads

Rust offers OS threads for heavy computational workloads and async/await (typically powered by Tokio or async-std) for I/O-bound concurrency.

Advantages:

  • Zero-Cost Abstractions: Rust’s state-machine generation for async code means tasks compile down to extremely efficient state transitions with zero runtime overhead.
  • Memory Footprint: Because the exact size of the state machine is known at compile time, memory allocation is statically verifiable and significantly smaller than even Go’s 2KB minimum.

Drawbacks:

  • Cooperative Preemption: If a Rust task blocks the thread (e.g., executing a long CPU-bound operation without yielding), it blocks the entire executor.
  • Ecosystem Fragmentation: Choosing a runtime binds you tightly to its ecosystem, unlike Go’s unified standard library.

Benchmark Analysis

In our testing, passing a million messages across 10,000 active concurrent actors revealed distinct profiles:

  • Latency: Rust maintained a strictly tighter latency distribution (p99 of 1.2ms) due to the absence of garbage collection pauses.
  • Throughput: Go matched Rust’s throughput up to the point of GC saturation, after which Go’s throughput dropped by ~15% relative to Rust.
  • Memory: Rust consumed roughly 40% less memory at peak concurrency.

Conclusion

If developer velocity and a massive ecosystem of pre-built integrations are paramount, Go’s model is unbeatable. But when memory constraints are tight and latency must remain absolutely predictable, Rust’s async model is the clear winner.

Thanks for reading. If you found this useful, feel free to DM me on X.