软件慢是因为设计烂,不是因为语言慢
用 Rust 或 C 重写一个慢程序,通常不会让它变快。Sean Goedecke 认为绝大多数的加速来自重新设计程序要做什么,而不是换一门跑得更快的语言。
中文
复制
很多人说,AI 会通过用更快的语言重写软件来让它变快,但这话通常不成立。大多数程序换成 C 或 Rust 重写,并不会真的快多少。
这怎么可能?有些语言确实比其他语言快上几个数量级。比如 C 能比 Python 快五十倍。那把 Python 程序用 C 重写,不就能快五十倍吗?并不能。软件性能通常由 I/O 决定,而不是语言的速度。在语言速度真正重要的地方,大多数慢语言早就有流行的库,可以释放出接近 C 的速度。总的来说,性能取决于设计,而不是选哪种语言。
真实程序大多是 I/O 密集型
设想一个带数据库的简单 CRUD 网站。对任意一个请求,可能有 50% 的请求时间1花在查询数据库上,取当前用户、取被请求的记录、取相关的用户成员关系或设置,等等。用 C 重写这个网站,只能加速剩下的那 50%,这就给你的优化空间设了硬上限2。最好的情况是把请求提速一倍(实践中大概更接近 1.5 倍)。这不算差,但离数量级的提升还差得远。
换句话说,大多数真实程序是“I/O 密集型”的,它们大部分时间都在等数据从某处输入或输出,而不是像“CPU 密集型”程序那样把时间花在主动做事上。I/O 密集型程序依赖的不只是数据库。程序经常要等某个第三方 API,或者等数据写入磁盘、从磁盘读出。你可以把 curl 里的代码提速 100 倍,大概根本察觉不到,因为 curl 的时间全花在等网络上了。
慢语言早就把活儿交给快语言了
那明显不是 I/O 密集型的工作呢?比如 wiki 要把存储的 markdown 变成可渲染的 HTML3。用更快的语言重写这部分,不就能大幅提速吗?确实能!所以做这件事的应用会用 cmark-gfm 这类库,把 markdown 渲染交给快语言去做。总的来说,大多数慢语言都能通过调用这类库写出非常快的程序。Python 是出了名的慢语言,但在机器学习、数学这类 CPU 密集型负载上却很有竞争力,因为它有一整套好用的库,可以把这些负载卸载到 C 代码上。如果一个 Python 程序 80% 的时间都在 GPU 上做矩阵乘法,那你几乎不可能靠用 C 重写来提速,因为这些矩阵乘法本来就已经跑在高度优化的 C 或汇编代码里了。
当然,有些程序确实在用慢语言做大量 CPU 密集型的工作。如果一个程序没怎么经过工程上的打磨,那里面大概率有不少唾手可得的优化空间。但这类程序往往并不重要。如果它们重要,早就有人在 AI 让优化变得(相对)容易之前动手优化了。这并不意味着找不到值得重写的有用程序。Actual Computer 最近用汇编重写了 tiktoken,带来了显著的4提速。但你没法靠这种方式革新某个计算领域。即便是快速分词,收益也只在边缘,因为分词耗时只占推理耗时的一小部分。
提速通常意味着重新设计
尽管如此,我认为大多数软件都远没有达到它应有的速度。对于大幅提升常用软件的性能(有没有 AI 都算),我是乐观的。但我认为这些提速大多会来自重新设计程序,而不是用更快的语言重写它。
在 2018 年的一篇文章里,Bryan Cantrill 讲述了他尝试把一个 C 程序重写成 Rust 的经历。过程很艰难,因为 C 程序经过精心优化的设计跟 Rust 的借用检查器合不来。他放弃了,干脆用最朴素的方式把程序写成了 Rust,预期会慢 20% 甚至更多。结果反而快了 30%。原因归结为 Rust 内部偏好 B 树而非二叉搜索树,而 B 树恰好是缓存效率更高的数据结构。所以即便在一个基本是 CPU 密集型的工作负载里,在一个非常擅长写高性能 C 的人手里,性能也由设计决定,而不是由语言速度决定。
这和我自己在高层语言里的经验一致。回想我做到过的那些显著提速,几乎无一例外都是重新设计软件、让它少干活:
- 缓存慢操作(通常是外部 API 调用)
- 把慢操作异步化,放到后台任务里,或者移出请求/响应生命周期
- 用并行代替串行执行操作(有时为了做到这一点,会把大操作拆开)
- 用单条 SQL 查询替换一组 SQL 查询(解决 N+1 问题)
- 用快的 SQL 查询替换慢的 SQL 查询
- 跳过不必要的慢操作(比如在分页视图里,只从当前页上的实体获取附属数据)
这些策略跟语言跑得快不快毫无关系。事实上,用一门慢的高级语言来写,反而可能让代码更容易优化,因为程序在做什么一眼就能看清。假如(出于某种原因)我一开始就用汇编重写那个 Web 服务器,那我就很难注意到 N+1 查询,也很难发现可以把某次 API 调用挪进后台任务里,诸如此类。
结论
现在人人都在让自己的 AI agent 把程序重写成 Rust、C 或汇编。这挺好玩,但算不上认真的优化尝试。能靠这种重写真正提速的程序,范围非常窄。
如果你想用 AI 让一切都变快(你应该这么做!),那该让 agent 去做的是对程序做性能分析、找出热点、提出重新设计程序行为的方案。偶尔你会碰到一个真正的 CPU 瓶颈热点,没法通过重构减少它的工作量。这种少见情况下,用更快的语言重写那一部分会有帮助。但一上来就整个重写,效果会远不如大多数人想象的那么好。
用另一种语言重写软件有很多正当理由,可移植性、效率、可维护性等等。但单就速度而言,你更应该把精力放在程序的设计上。