软件变慢的原因持续存在
摘要
作者认为,尽管LLMs使某些优化变得更廉价,但由于异步工作流中对延迟的容忍度增加以及其他实际约束,软件仍然可能变慢。
<p><a href="https://lobste.rs/s/zibquu/there_continue_be_reasons_for_software_be">评论</a></p>
查看缓存全文
缓存时间: 2026/08/22 15:08
# 软件性能低下的原因依然存在
来源:https://typesanitizer.com/blog/performance-issues.html
丹·卢(Dan Luu)最近发表了一篇题为“软件没有理由再慢下去了”(https://danluu.com/perf-opt/)的博客文章,文中探讨了借助大语言模型(LLM),构建专用解决方案(例如即时编译器、针对搜索类问题的索引)以及针对特定工作负载的优化等任务如今变得更加经济高效。
> 我们尚未达到需要用汇编语言编写所有代码的程度,但诺兰·劳森(Nolan Lawson)关于测试的某些观点——你现在可以选择自己想要多少个bug(https://nolanlawson.com/2026/08/16/you-can-just-choose-how-many-bugs-you-want-now/),我在此处表达得不那么精辟——正在性能领域变得愈发真实。
我认为,这段文字的初衷虽好,但并不正确。这与劳森的观点、以及形式化方法倡导者认为越来越多比例(此处使用“比例”一词是刻意的。如果换成“数量”,那就根本没什么好争论的了)的软件将经过形式验证的主张一样,都是出于好意但不准确的观点。明确地说,我非常支持更好的测试、形式化方法的运用以及性能优化工作!我在多份工作中都从事过相关领域的工作,包括我目前的职位!只是我不同意这些关于未来的预测。
本质上,这些观点提出的论据大致如下(我用美元符号表示,但如果你想,可以替换成“工程师E的时间”):
> 1. 理想的特性 X 在预算 $B 之下,过去需要花费 $A。
> 2. 在大语言模型出现之前,人们不追求 X 的原因是其超出了预算。
> 3. 大语言模型出现后,实现 X 的成本降至 $A/N < $B,因为 N >> 1。
> 如果这些前提成立,那么人们现在会花 $A/N 来获得 X。
表面上看,如果你个人发现大语言模型在提升特性 X 方面很有用,这个论点似乎合理。你可能会想:“这还假设了人们是拥有完美知识的理性经济参与者。” 是的,确实如此。本文的大部分内容将基于这一假设展开,并说明即使在如此强的假设下,事情也可能出错。现实中,人们确实并非完全理性,也并非拥有完美知识。我在此文中忽略了这一点,因为关于这个话题已有大量论述。但只有在前提成立的情况下,该论点才能在实践中行得通。
我同意在某些情况下这些前提成立。那么,某些具有深厚领域专业知识的资深人士(如卢文章中引用的那些人)是否会进行大量优化,或者加入能发布比以前多得多的优化成果的团队?嗯,我认为这肯定会发生。
然而,根据我目前的观察,前提成立的情况远少于不成立的情况。
在本文中,我将举一些我所见的前提不成立的例子。
## 对“非X”的容忍度提高了
(或者说:“X的理想程度降低了”)
大语言模型出现的一个不同之处在于,由于智能体循环迭代的延迟,你原本同步进行的工作现在可能需要异步完成。
一个具体的例子是,我最近一直在为公司的代码仓库优化`git`性能。如果把我如今在顺利情况下看到的性能数据,在2022年时交给我,并告诉我人们认为这些数字可以接受,我可能会投以非常怀疑或困惑的目光。
另一个例子是,基于大语言模型的自动补全功能刚推出时,延迟比标准IDE的自动补全要高得多。后来随着Cursor等编辑器引入了更小、专用的模型以实现更快的补全,情况发生了变化。在那段时间前后,如果你看过开发者现场编码的视频,会注意到他们在等待大语言模型建议时会有短暂的停顿。但从历史上看,自动补全功能被认为备受推崇的原因之一就是其“即时”反馈!
这一点同样适用于编译速度、链接时间、运行测试所需时间等。总的来说,人们对同步工作和异步工作的容忍度差异很大。
如果你是一个重视性能的人,可能很难接受人们实际上愿意忍受软件中更差的性能,尤其是当你已经认为该软件的性能“太慢”时。如果同样的人愿意为获得更多功能*特意*忍受更差的性能(特别是当你已经认为该软件“过于臃肿”时),那就更加令人沮丧了。
## 预算从一开始就是零
除了那些给予开发者良好待遇、提供相当自主权且薪酬优厚的技术公司外,在许多公司中,软件职能常被视为“成本中心”而非“利润中心”,这很常见。
可能连CI流程都没有——完全依赖手动QA。获得预算批准可能需要很长时间。
然而,业务可能依然表现良好!例如,公司可能拥有政府授予的垄断地位。或者可能拥有某种其他形式的权力(https://7powers.com/)。
如果环境完全专注于降低成本,那么要让经理相信投入性能优化工作的投资回报率(ROI)可能需要相当大的努力。或许,将这份努力花在其他地方更值得。
## 预算在大语言模型出现后被削减了
假设预算从非零开始。例如,你可能每个季度已经在性能优化上花费了大约1周时间。根据你过去的经验,这听起来可能极其慷慨或低得可怜。我知道范围很广。🙂
即便如此,这里隐含了一个假设:获取特性 X 的预算 $B 在大语言模型出现后保持不变。这个假设常常不成立。
如果你浏览 r/experienceddevs 论坛(https://old.reddit.com/r/experienceddevs),经常能看到工程师们谈论过去一年项目时间线如何被压缩得更紧,因为管理层期望由于大语言模型的使用,事情完成的时间应该大大减少。
## 成本降低因子 N 被高估了
实现一个优化是一回事。将优化发布到广泛使用的生产软件中是另一回事。建立一个棘轮机制(https://qntm.org/ratchet)以防止代码未来回退又是一回事。确保该棘轮机制可靠(低/无假阴性/假阳性)、高效(运行足够快)且稳定(不需要持续维护)则又是一回事。
在我之前关于代码审查的文章(https://typesanitizer.com/blog/code-review.html)中,我举了一个例子:一位同事试图通过将操作移至后台进程来降低延迟,结果却增加了锁获取失败的风险。
更普遍地说,不熟悉系统的人很容易提出“性能优化”建议,而这些建议实际上损害了设计中对正确性至关重要的某个方面。
一个更突出的例子是,Bun的创建者杰瑞德·萨姆纳(Jarred Sumner)据称拥有一个Zig编译器的分支,其中包含了并行化的语义分析和代码生成(https://xcancel.com/bunjavascript/status/2048427636414923250?s=20)。
Zig编译器的关键贡献者之一马修·卢格(Matthew Lugg)阐述了为什么这个改动无法被上游采纳(https://ziggit.dev/t/bun-s-zig-fork-got-4x-faster-compilation-times/15183/18)(即使不考虑Zig关于不接受LLM贡献的政策):
> 并行语义分析是Zig编译器长期以来明确规划的特性,它极大地影响了自托管Zig编译器的设计。然而,正确实现此特性不仅对编译器实现有影响,对Zig语言本身也有影响!因此,为了在实现此特性时不产生大量bug和不一致,我们需要对语言进行更改。(……)重写后的类型解析语义旨在避免这些问题,但Bun的Zig分支并未包含这些更改(也未能解决设计问题),这意味着他们的并行化语义分析实现将表现出不确定行为。这对于大多数严肃的开发者来说几乎是无法接受的:你不希望你的编译过程有30%的几率随机失败并给出无意义的错误。
看待这一点的另一种方式是,编写代码的成本只是全貌的一部分。它可能不是主要成本。
举两个高层次的例子:
* 如果已经有大量数据以不利于优化处理的格式存储,那么优化性能的成本需要考虑将数据重新组织为正确形式的成本,同时保持现有读写路径的可靠性、性能和正确性。还需要考虑迁移现有代码的成本。更糟糕的是,你可能甚至不知道所有的读写路径,那么查明这些路径的成本也需要纳入考量。
* 如果你为数据处理付费,尝试不同的策略来优化计算本身可能就很昂贵。例如,如果你只在1/1000次CI运行中遇到一个不稳定bug,并且无法在CI环境之外重现它,那么用CI时间以足够细节重现该bug的成本很容易就会超过编写修复程序的成本。
与此相关的是,另一个挑战是更难估算与维护相关的长尾成本:因优化复杂性导致的代码理解能力丧失、需要雇佣更多能够维护系统的资深人员、额外的正确性检查等等。
## 预算从来不是不追求X的原因
我认为,与其考虑成本,不如用优先级来思考哪些工作能被完成,这样更有用。
为简单起见,假设我们在讨论基于冲刺周期的规划。比如说,平均而言,在大语言模型出现之前,团队中每个人每个冲刺处理4个工单。假设对某人来说,性能优化工作通常排在第6位。在每个冲刺4个工单的情况下,这意味着性能优化工作将作为愿景目标,在多个冲刺中一直停留在冲刺规划板上。
如果你的经验与我类似,很可能遇到过优先级过低、跨越多个冲刺的工单,过一段时间后,有人会说:“嘿,我们不会处理这个,所以它显然不是高优先级,我们是否应该将其标记为‘已取消’,至少等到团队外的人再次提出时再说?”
现在,在大语言模型出现后,假设每个人可以处理12个项目。这个列表会保持不变,只是从待办事项列表中拉取更多项目,让每个人都有足够的工作量吗?
我怀疑对大多数人来说,答案是否定的。如果你在大语言模型出现之前未能成功主张将性能优化工作列为更高优先级,那么不清楚为什么在大语言模型出现后你就能做到。当然,你可以私下进行(即“先斩后奏”),但在大语言模型出现之前你也可以这么做。
## 重新审视卢的例子
在卢的文章中,有两个例子我想讨论,因为代码是公开的,并且它们代表了复杂的任务:
1. **pgrust**:用Rust重写的Postgres。
2. **FRE**:由运行了一个多月的智能体循环构建的正则表达式引擎。
---
对于 **pgrust**,其亮点是在ClickBench上表现出色,据称是由于使用了大语言模型驱动的优化。另一个被提及的观点是,据称人们(在数据库领域)不编写JIT(即时编译器)是因为那太困难了,但大语言模型使其变得可行。
粗略来看,目前尚不清楚其出色的性能有多少归功于针对基准测试的性能“黑客”手段,又有多少归功于优秀的、可推广的设计。例如,如果你查看其成本模型(https://sourcegraph.com/r/github.com/malisper/pgrust@438c8c420b96b23ca61927ba57e608839f86e935/-/blob/crates/backend/optimizer/path/costsize/src/runtime_model.rs?L171-180),会发现它明确地到处引用ClickBench。
如果你查看其性能引导优化(PGO)语料库(https://sourcegraph.com/r/github.com/malisper/pgrust/-/blob/benchmarks/pgo/corpus/analytics-hits.sql),里面包含像这样的查询:
```sql
-- A15 composite (smallint, string) group + count
SELECT URLCategoryID, UTMSource, COUNT(*) AS n FROM hits WHERE UTMSource <> '' GROUP BY URLCategoryID, UTMSource ORDER BY n DESC LIMIT 12;
```
如果你看ClickBench的第15行(https://sourcegraph.com/r/github.com/malisper/pgrust/-/blob/benchmarks/queries.sql?L15):
```sql
SELECT SearchEngineID, SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10;
```
稍加注意,你会意识到它们在概念上是相同的查询,只是改了一些名字,常量(LIMIT值)略有不同。
这种情况适用于许多用于PGO的查询。也许我理解有误,但这看起来是明显的针对基准测试的过拟合。
关于其他人不编写JIT的观点,至少有几个数据库引擎实现了JIT。
* **Umbra(HTAP)** 及其商业化分支 **CedarDB**:Umbra目前在ClickBench上名列前茅,CedarDB紧随其后。Umbra使用了一个两层JIT系统(底层使用自定义编译器,高层使用LLVM),其代码生成器和编译器一起(包括头文件、实现和测试)大约包含40K行代码(https://link.springer.com/content/pdf/10.1007/s00778-020-00643-4.pdf)。
* **SingleStore(HTAP)**:其SIGMOD 22论文(https://dl.acm.org/doi/pdf/10.1145/3514221.3526055)明确引用了关于为HyPer(Umbra的前身)编译查询的工作(https://vldb.org/pvldb/vol4/p539-neumann.pdf)。
* **Amazon Redshift(OLAP)**:将查询编译为C++。我不确定这是否算数?在我心中,这仍然是JIT,只是通过C++实现,但不清楚pgrust作者是否会认为这是一种JIT。
* **Apache Impala(OLAP)**:生成LLVM IR(https://github.com/apache/impala/blob/master/be/src/codegen/llvm-codegen.cc)
我猜想还有更多例子。
---
对于 **FRE**(https://github.com/danluu/fre),其README声明:
> FRE是一个在极少人工干预下使用大语言模型生成的正则表达式引擎。它似乎过拟合了BurntSushi的rebar基准测试,整体性能并不好,尽管在某些用例下它实际上相当快。详情见此文章(https://github.com/danluu/fre)。当你不在意编译时间并选择AOT/优化编译模式时,它有时(但并非总是)很快。其他让它表现快的情况通常更特殊。
这里有个快速问题。估算以下项目的实现代码行数(SLOC,排除生成代码和测试):
* rust-lang/regex
* danluu/fre
我让一个大语言模型估算这些数字,它给出的结果是rust-lang/regex为35K,danluu/fre为670K。不,我没弄错一个零。
另一个参考点是,Go编译器和标准库合计(排除生成代码、测试和vendored目录)大约有680K行代码。
卢对偏好提出了以下说明:
> 没有特别的理由使用一个“软件工厂”正则表达式引擎,如果它在留出测试集上无法超越一个经过充分测试的引擎,但FRE的一个显著特点是其原生AOT
相似文章
软件没有理由再慢了
该文章指出,LLMs和AI工具正在降低软件性能优化的门槛,使得之前因成本过高而无法实施的自定义适配(如JIT编译器和正则表达式引擎)成为可能。
引用布莱恩·坎特里尔
布莱恩·坎特里尔批评LLM缺乏人类懒惰带来的优化约束,认为LLM会不必要地使系统复杂化而非改进,并强调人类时间限制推动了高效抽象的发展。
为什么我仍然是一个怀疑论者
作者对LLMs在软件开发中的有效性表示怀疑,指出缺乏关于生产力提升的独立研究以及AI生成代码的质量问题。
2倍,而非10倍:2026年使用LLM编程
作者认为,由于LLM能够处理易于验证的任务,目前可为编程提供约2倍的生产力提升,但根本性限制使其无法实现10倍改进;进一步的提升将来自围绕现有能力重构工具,而非模型改进。
慢速软件:为高延迟系统开发辩护
文章认为,AI编程加速了开发速度与系统重要性的脱钩,导致关键系统变得脆弱且故障波及范围广,并倡导实施强制谨慎设计的‘慢速软件’。