土法炼钢兴趣小组的算法知识备份

Go vs C vs Rust:系统编程实测

目录

每次有人问”写系统程序用什么语言”,回答都是立场先行的:C 程序员说 C,Rust 程序员说 Rust,Go 程序员说 Go。每个人都有自己的 benchmark 证明自己是对的。

这篇不站队。我用同一个任务——一个 TCP 代理服务器——分别用 Go、C、Rust 实现,然后在五个维度上正面对比。数据说话,不说教。

测试任务:TCP Proxy

功能:接受客户端连接,转发到后端服务器,双向透传数据。最小的 L4 代理。

选这个任务是因为它恰好覆盖了系统编程的核心操作:网络 I/O、并发管理、内存拷贝、错误处理。

三个实现的代码量:

语言 实现方式 代码行数 依赖
C epoll + non-blocking 380
Go goroutine + io.Copy 85 标准库
Rust tokio + async/await 120 tokio

维度一:吞吐量

测试:1000 个并发连接,每个连接持续双向发送 1KB 数据块。测量每秒总数据传输量。

并发 C (Gbps) Go (Gbps) Rust (Gbps)
100 8.2 7.5 8.0
1,000 7.8 7.1 7.6
10,000 7.2 5.8 7.0
50,000 6.5 3.2 6.3

低并发下三者差距不大——瓶颈在网络栈和网卡,不在应用。

10,000 并发以上 Go 开始掉队。原因:goroutine 调度器在高并发下的开销。每次 goroutine 切换需要保存/恢复栈帧(Go 调度器那篇详细分析过这个),50,000 个 goroutine 的调度开销相当可观。Go 的 io.Copy 也不如 C/Rust 的 splice 或 zero-copy 路径高效。

C 和 Rust 差距始终在 5% 以内。Rust 的 tokio 底层也是 epoll(或 io_uring),抽象层的开销在 1-2% 左右。

维度二:延迟

测试:单个连接,发送 64 字节 -> 等待后端 echo -> 转发回客户端。测量端到端延迟。

分位数 C Go Rust
P50 18 us 35 us 20 us
P99 42 us 180 us 55 us
P99.9 85 us 2.1 ms 120 us
P99.99 210 us 8.5 ms 350 us

Go 的尾延迟比 C/Rust 高一个数量级。P99.99 差 40 倍。

原因一:GC。Go 的 GC 在 1.21+ 已经非常好了,P50 STW 在 100us 以内。但 P99.99 的 GC pause 可以到几毫秒,尤其是在堆上有大量小对象时。

原因二:goroutine 调度延迟。goroutine 不是被调度就能立刻运行——它需要等 P (处理器) 空闲。在有其他 goroutine 竞争时,调度延迟是不确定的。

C 的尾延迟最低且最可预测。没有 GC、没有 runtime、内存分配模式完全可控。

Rust 介于两者之间。tokio 的 work-stealing 调度器有少量延迟抖动,但没有 GC 停顿。

维度三:内存占用

测试:维持 10,000 个空闲连接,测量 RSS。

指标 C Go Rust
基础 RSS 1.2 MB 12 MB 3.5 MB
10K 连接 RSS 18 MB 85 MB 22 MB
每连接开销 1.7 KB 7.3 KB 1.9 KB

Go 的每连接开销是 C/Rust 的 4 倍。原因:每个 goroutine 的初始栈是 8KB(会动态收缩,但空闲状态最小 2KB)。10,000 个连接 = 10,000 个读 goroutine + 10,000 个写 goroutine = 20,000 个 goroutine。

C 用 epoll 事件驱动,每个连接只是一个 fd + 一小块 state 结构体。Rust 用 tokio 的异步 task,每个 task 是一个 Future 状态机,占用通常 <200 字节。

在需要支持百万级连接的场景(如 IM 服务器、推送网关),内存占用是关键约束。Go 需要 ~7GB 支撑 100 万连接,C/Rust 只需要 ~2GB。

维度四:编译速度与开发体验

指标 C Go Rust
编译时间(全量) 0.3s 1.2s 15s
编译时间(增量) 0.1s 0.4s 3s
编译器错误可读性 极好
Debug 工具 gdb/valgrind delve/pprof gdb/miri
包管理 无(手写 Makefile) go mod cargo

Go 的开发速度明显领先。编译快、错误信息清晰、go mod 开箱即用、pprof 性能分析一行代码嵌入。

Rust 的编译慢是真痛点。15 秒全量编译对 380 行代码来说太慢了(主要是编译 tokio 依赖)。但增量编译 3 秒可以接受。编译器错误信息是三者中最好的——不只告诉你哪里错了,还告诉你怎么改。

C 的编译最快,但没有包管理是现代开发中的硬伤。手动管理 header 依赖、链接库版本,在大型项目中是噩梦。

维度五:并发模型

维度 C Go Rust
并发原语 pthread / epoll goroutine / channel async/await / tokio
数据竞争检测 运行时 (TSan) 运行时 (race detector) 编译时 (Send/Sync)
死锁检测 无内建 无内建 无内建
Channel 无内建 内建 crossbeam / tokio mpsc
共享状态 手动 mutex sync.Mutex Mutex (类型绑定)

Go 的并发模型对开发者最友好。go func() 启动一个 goroutine,channel 传数据,不需要思考”这个数据要不要加锁”——因为 Go 鼓励”通过通信共享,而非通过共享通信”。

Rust 的并发模型对正确性最好。SendSync trait 在编译时阻止 data race。Mutex<T> 把锁和数据绑在一起——你不可能忘记加锁就访问数据。但学习曲线陡峭。

C 的并发模型给你最大自由度和最大犯错空间。pthread + atomic 可以做任何事,包括用一种极其隐蔽的方式写出 data race。

选型建议

场景 推荐 理由
业务后端 / 微服务 Go 开发速度快,GC 开销可接受
延迟敏感系统 (P99.9 < 1ms) Rust 或 C 无 GC,延迟可预测
百万级连接 C 或 Rust 每连接内存开销低
嵌入式 / 内核模块 C 无 runtime
CLI 工具 Rust 或 Go 编译为单一二进制
原型开发 Go 编译快,标准库全
已有 C 代码库扩展 C 或 Rust (FFI) 避免重写

没有一个语言在所有维度上赢。 Go 赢开发速度和并发友好度,C 赢延迟和控制精度,Rust 赢安全性和内存效率。

选语言不是选信仰。是选你的项目在哪个维度上的约束最紧。


延伸阅读:

参考资料:

  1. TechEmpower Framework Benchmarks – 多语言 Web 框架性能横评
  2. The Benchmarks Game – 多语言 micro-benchmark 集合

By .