同一个网关,四种语言
被测对象是一个真实的聚合网关:登录与多租户鉴权、机器 ed25519 签名认证、跨机会话聚合、透明反代、Web Push(RFC 8291 加密 + VAPID 签名)、三层限流、用量记账——Python 版 4,617 行。四版实现的功能逐字段对齐,共享同一份密码学一致性测试向量(conformance),四版全部通过:
| 实现 | 技术栈 | 规模 | 状态 |
|---|---|---|---|
| Python | FastAPI + SQLAlchemy + httpx | 4.6k 行 | 初版,退役 |
| Rust | axum + tokio + sqlx | ~8k 行 | 上线一天,退役 |
| C++ | boost.asio + Beast + Boost.MySQL + hiredis | ~15k 行 | 当前生产版 |
| Go | net/http 标准库 + go-sql-driver | 7.4k 行 | 对比基准版 |
四版共用同一套 conformance fixture(AES-GCM / bcrypt / Ed25519 请求签名 / SSH 密钥派生 / RFC 8291 推送加密的确定性向量),24 项断言四版逐字节互通——这是“性能对比”成立的前提。
数字说话
环境:树莓派 5(4×Cortex-A76),MySQL 与被测网关同机,本地压测端无限流旁路。两个探针端点:/ping(纯框架开销,零逻辑)和 /healthz(真实业务路径:鉴权 + 两次数据库查询 + JSON 组装)。
单核吞吐(RPS,越高越好)
| 端点 | C++ | Rust | Go | Python | ||||
|---|---|---|---|---|---|---|---|---|
| /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 @ c20 | C++ | Rust | Go | Python |
|---|---|---|---|---|
| 单核 | 64.6k | 52.7k | 36.5k | ~1.1k |
| 双核 | 100.3k | 94.3k | 59.8k | 472 ↓ |
| 四核 | 105.0k | 98.6k | 87.0k | 474 |
四线程时三编译版全部撞上压测端自身的饱和墙(单核压测工具约 91k RPS),C++ 的真实上限已测不出。Python 双进程低并发反而比单进程更差——多进程 + 低并发下连接分配不均。
三个反直觉的发现
换个数据库,排名整个反转
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 倍。
官方库用错姿势,比手写还慢
C++ 接 Redis 用官方 hiredis 测了四种姿势,差距大到离谱:
| 集成方式 | RPS |
|---|---|
| hiredis 异步 API + 事件循环桥(天然 pipeline) | 27.5k |
| 手写 RESP 协议泵 | 13.1k |
| hiredis 同步 API,IO 线程直调 | 10.1k |
| hiredis 同步 API + 线程池 | 9.3k |
同一个官方库,姿势不同差 3 倍。同步 API 扔进线程池,光两次线程切换就吃掉了库本身省下的时间。
天花板参照系
同一台树莓派上裸跑 redis-benchmark,单核 GET 是 95k。也就是说纯 Redis 服务自身是这个硬件上“每请求一次读写”服务的物理上限——C++ 网关在业务端点上做到了这个上限的 31%,Go 10%,Python 0.5%。知道天花板在哪,性能优化才不是玄学。
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 的通用池就够了——知道自己付的每一分钱买了什么,比盲目追求最快的那个更重要。
人的位置上移了
这次全程人做了三件事,恰好都不是写代码:
写代码的稀缺性在下降,但定义什么算对、在无限的可能里收敛到该做的那一个,这件事的稀缺性在上升。
结语
那台树莓派上,C++ 版网关单核 64.6k、p99 半毫秒,是物理上限的 68%;而同一个压测里的 Python 版,在 20 并发下堆积过 457 秒的请求。两者之间隔着的不只是 50 倍的性能,还有一个时代的换挡:
当“人写不动”不再是理由,我们就终于可以只问“它该有多快”。