同一个聚合网关,Python → Rust → C++ → Go 四版实现的全量压测,与一个老问题的重新审视:语言选型到底是在为谁优化。

§1

同一个网关,四种语言


被测对象是一个真实的聚合网关:登录与多租户鉴权、机器 ed25519 签名认证、跨机会话聚合、透明反代、Web Push(RFC 8291 加密 + VAPID 签名)、三层限流、用量记账——Python 版 4,617 行。四版实现的功能逐字段对齐,共享同一份密码学一致性测试向量(conformance),四版全部通过:

实现技术栈规模状态
PythonFastAPI + SQLAlchemy + httpx4.6k 行初版,退役
Rustaxum + tokio + sqlx~8k 行上线一天,退役
C++boost.asio + Beast + Boost.MySQL + hiredis~15k 行当前生产版
Gonet/http 标准库 + go-sql-driver7.4k 行对比基准版

四版共用同一套 conformance fixture(AES-GCM / bcrypt / Ed25519 请求签名 / SSH 密钥派生 / RFC 8291 推送加密的确定性向量),24 项断言四版逐字节互通——这是“性能对比”成立的前提。

§2

数字说话


环境:树莓派 5(4×Cortex-A76),MySQL 与被测网关同机,本地压测端无限流旁路。两个探针端点:/ping(纯框架开销,零逻辑)和 /healthz(真实业务路径:鉴权 + 两次数据库查询 + JSON 组装)。

单核吞吐(RPS,越高越好)

端点C++RustGoPython
/ping 64.6k 52.7k 36.5k 1.2k
/healthz(MySQL) 5.6k 2.7k 4.6k
/healthz(Redis) 27.5k 21.9k 9.6k 488

单核 CPU 占用四版均经 /proc/PID/stat utime+stime 增量核验,全部跑满(99–101%)。“瘫”指并发 20 时请求堆积、平均响应 457 秒。

几组值得咀嚼的倍数:纯框架路径上 C++ 是 Python 的 52.7 倍;业务路径上差距收窄到 4.9 倍(数据库延迟摊薄了框架差距);p99 延迟 C++(0.85ms)是 Python(124ms)的 1/145。

多核扩展

ping @ c20C++RustGoPython
单核64.6k52.7k36.5k~1.1k
双核100.3k94.3k59.8k472
四核105.0k98.6k87.0k474

四线程时三编译版全部撞上压测端自身的饱和墙(单核压测工具约 91k RPS),C++ 的真实上限已测不出。Python 双进程低并发反而比单进程更差——多进程 + 低并发下连接分配不均。

§3

三个反直觉的发现


发现 01

换个数据库,排名整个反转

MySQL 后端下排名是 C++ > Go > Rust;把 healthz 的两次 SQL 查询换成两次异步 Redis GET,排名变成 C++ > Rust >> Go,且 Rust 从 2.7k 跳到 21.9k(8.2 倍)——同一套运行时,换个客户端排名就翻篇。

后来的一次核查把这个判断坐实成了数字:把 healthz 的三个真实查询做成驱动级微基准(同一台 MySQL、单 worker 串行循环),Rust 的 sqlx 每查询 223µs,换成 Rust 另一主流异步驱动 mysql_async 是 162µs(快 38%);而 C++ 的 boost.mysql 约 45µs、Go 的 go-sql-driver 约 87µs——Rust 生态两个主流 MySQL 驱动都显著慢于 C++/Go 同类。Rust 版的 2.7k 不是 tokio 慢,是 sqlx 的税。

评估一门技术栈的性能,先要分清量的是哪一层——同是“连 MySQL”,驱动实现的差距可以到 5 倍。

发现 02

官方库用错姿势,比手写还慢

C++ 接 Redis 用官方 hiredis 测了四种姿势,差距大到离谱:

集成方式RPS
hiredis 异步 API + 事件循环桥(天然 pipeline)27.5k
手写 RESP 协议泵13.1k
hiredis 同步 API,IO 线程直调10.1k
hiredis 同步 API + 线程池9.3k

同一个官方库,姿势不同差 3 倍。同步 API 扔进线程池,光两次线程切换就吃掉了库本身省下的时间。

发现 03

天花板参照系

同一台树莓派上裸跑 redis-benchmark,单核 GET 是 95k。也就是说纯 Redis 服务自身是这个硬件上“每请求一次读写”服务的物理上限——C++ 网关在业务端点上做到了这个上限的 31%,Go 10%,Python 0.5%。知道天花板在哪,性能优化才不是玄学。

§4

AI 时代的思考:抽丝剥茧,回归技术本身


到这里是常规的性能文。但这次实验真正让我停下来想的,是另一个问题:这四版代码,是谁写的?

答案:Python 版是“人类时代”的产物;Rust 版是人类主导的第一次重写;而 C++ 的性能挖掘(perf 采样、九轮优化、43.7k 到 64.6k)和整个 Go 版(7,400 行、24 项密码学对拍全部通过、零 CGO 交叉编译),是 AI 在一次会话里完成的。包括那些过去最劝退 C++ 的脏活:boost.asio 协程与 hiredis 事件桥的线程安全(试了 strand、mutex、thread-local 三种方案并实测各自代价)、RFC 8291 加密里那个 0x02 padding delimiter、甚至 shell 传参吃掉 base64 padding 这种边角坑。

过去我们为什么选 Python

不是因为它快——它从来没有快过。是因为人的时间是稀缺资源:Python 一天能上线的服务,C++ 可能要一周;团队成员的可读性、招聘池、生态成熟度,都在给“慢但快出活”的语言投票。性能损失不是无知,是理性定价:我们用 50 倍的运行时开销,换取 5 倍的开发吞吐。

语言选型从来不是技术判断,是“为谁的效率优化”的判断。

AI 把这个约束抽掉了

当写 C++ 的边际成本趋近于写 Python——AI 读得懂 4,600 行遗留代码,写得出一整套等价实现,记得住 hiredis 回调签名在 1.2.0 从 int 改成了 void,还能自己压测、自己定位到“每秒刷 journal 的日志级别”——那个维持了三十年的交换就不成立了。约束条件变了,最优点就会移动。

于是技术选型可以第一次真正“抽丝剥茧”:剥掉“团队会不会写”这层人的约束,剥掉“生态里只有 Python 客户端”这层路径依赖,剩下的问题朴素得近乎物理学——这条请求路径上,每毫秒花在了哪里?epoll 唤醒、内存分配、HTTP 头解析、加密握手,各自的常数是多少?该选什么,答案常常就是那个过去“不敢选”的选项。

但回归技术本身 ≠ 无脑选最难的

这次对比里我最喜欢的数字,反而是 Go 版的一个“失败”:纯框架路径它只有 C++ 的 56%,垫底;但在真实用户视角(经公网链路)的业务端点上,它与 C++ 只差 8%——链路延迟把框架差距摊薄到无感。而它的工程账面是:7,400 行覆盖全部功能、静态单文件 7.6MB、零 CGO、交叉编译一分钟。

AI 时代真正的变化不是“必须用 C++”,而是决策空间变宽了:以前 C++ 根本不在菜单上(没人写得起),现在它在菜单上,和 Go、Rust 一起,按“这条路径物理上该多快、这个团队要维护多少年”来定价。有的路径值得 hiredis 异步桥的 27.5k,有的路径 9.6k 的通用池就够了——知道自己付的每一分钱买了什么,比盲目追求最快的那个更重要。

人的位置上移了

这次全程人做了三件事,恰好都不是写代码:

定契约 那份 24 项的 conformance fixture 是四版互通的锚——AI 之间靠它对齐,而不是靠口头约定。
做裁决 “cpp 应该用官方的 hiredis”是一个判断,之后才有异步桥登顶 27.5k。
把握停的时机 asio 与 hiredis 桥在四线程下的挂起是块硬骨头,判断“不值得再挖”,比挖下去更需要经验。

写代码的稀缺性在下降,但定义什么算对、在无限的可能里收敛到该做的那一个,这件事的稀缺性在上升。

§5

结语


那台树莓派上,C++ 版网关单核 64.6k、p99 半毫秒,是物理上限的 68%;而同一个压测里的 Python 版,在 20 并发下堆积过 457 秒的请求。两者之间隔着的不只是 50 倍的性能,还有一个时代的换挡:

当“人写不动”不再是理由,我们就终于可以只问“它该有多快”。

数据说明:全部数字为 2026-08-16 于树莓派 5(8GB)实测,压测端为 tokio 实现的高并发 HTTP 工具,四版服务端 CPU 占用均经 /proc 核验跑满对应核数;多组关键数字经过跨日复测,偏差 <2%。生产 C++ 版经 25 项 conformance 与线上回归验证。