当编译器让你惊喜
摘要
Matt Godbolt 探讨了编译器优化如何将 O(n) 求和循环转换为 O(1) 的闭式解,突出了 Clang 和 GCC 如何采用循环展开和数学简化等复杂技术来大幅提升代码性能。
<p><a href="https://lobste.rs/s/vugypt/when_compilers_surprise_you">评论</a></p>
查看缓存全文
缓存时间: 2026/04/20 14:44
# 编译器令人惊喜的时刻 — Matt Godbolt 的博客
来源:https://xania.org/202512/24-cunning-clang
作者:我,由 LLM 校对。详细信息见文末。
编译器时不时会用一些真正聪明的技巧让我惊叹。当我第一次看到这个优化时,几乎不敢相信。我在研究循环优化时写了这个简单的函数,它用来求和到给定值的所有数字:
到目前为止看起来还不错:GCC 做了一些初步检查,然后进入一个循环,使用 `lea` 高效地求和(我们之前见过这个(https://xania.org/202512/02-adding-integers))。但仔细看循环,我们会发现一些不寻常的东西:
```
.L3:
lea edx, [rdx+1+rax*2] ; result = result + 1 + x*2
add eax, 2 ; x += 2
cmp edi, eax ; x != value
jne .L3 ; keep looping
```
编译器巧妙地意识到它可以一次处理两个数字¹(https://xania.org/202512/24-cunning-clang#fn:check),利用了这样一个事实:我们会加上 `x` *和* `x + 1`,这等于加上 `x*2 + 1`。非常巧妙,我同意!
如果你把优化器调到 `-O3`,你会看到编译器更加努力地使用并行加法来向量化循环。非常聪明。
这都是 GCC 的表现。让我们看看 clang 对我们代码的处理:
这是我差点从椅子上摔下来的地方:**根本没有循环**!Clang 检查 `value` 是否为正,如果是,它执行:
```
lea eax, [rdi - 1] ; eax = value - 1
lea ecx, [rdi - 2] ; ecx = value - 2
imul rcx, rax ; rcx = (value - 1) * (value - 2)
shr rcx ; rcx >>= 1
lea eax, [rdi + rcx] ; eax = value + rcx
dec eax ; --eax
ret
```
对我来说,这到底在干什么根本不明显。通过回推数学,这等价于:
```
v + ((v - 1)(v - 2) / 2) - 1;
```
展开括号:
```
v + (v² - 2v - v + 2) / 2 - 1
```
重新整理一下:
```
(v² - 3v + 2) / 2 + (v - 1)
```
将 `(v - 1)` 乘以 2 / 2:
```
(v² - 3v + 2) / 2 + (2v - 2)/2
```
合并这些项并化简:
简化和因式分解得到 `v(v - 1) / 2`,这就是"整数和"的闭式解!真是令人惊叹²(https://xania.org/202512/24-cunning-clang#fn:why)——我们从按照编写方式的 O(n) 算法,转变成了 O(1) 的算法!
我喜欢尽管和编译器打了二十多年交道,它们仍然能让我惊喜和欣喜。多年来为了让编译器优秀而投入的经验和工作确实令人谦卑,也令人鼓舞。
我们快到这个系列的尾声了——还有很多要说,但那得等下一次了。明天会有点不同:到时见!
*观看伴随这篇文章的视频(https://youtu.be/V9dy34slaxA)。*
---
*这篇文章是编译器优化降临节 2025(https://xania.org/AoCO2025-archive) 的第 24 天,一个 25 天的系列,探索编译器如何转换我们的代码。*
*← 有点变化(https://xania.org/202512/23-switching-it-up) | 谢谢(https://xania.org/202512/25-thank-you) →*
*这篇文章由人类(Matt Godbolt(https://xania.org/MattGodbolt))撰写,由 LLM 和人类审阅和校对。*
*在 Patreon(https://patreon.com/c/mattgodbolt) 或 GitHub(https://github.com/sponsors/compiler-explorer) 上支持 Compiler Explorer,或在 Compiler Explorer Shop(https://shop.compiler-explorer.com/) 购买 CE 产品。*
发布于 2025 年 12 月 24 日 CST 06:00:00。
相似文章
信任你的编译器:现代C++
本文对比了旧的C++性能技巧与现代编译器的能力,表明编译器现在能够将朴素代码优化得比手工调整的技巧更好。包含在AMD Zen 5上使用Clang 21的基准测试。
查询循环:编译器谋杀之谜
一位 Ferrocene/Rust 编译器工程师详细描述了一场为期一周的调试历程,该崩溃由查询循环引起,最终揭示了三个相互作用的错误,导致了OOM和无限循环。
为什么ML/OCaml适合编写编译器(1998)
这篇1998年的文章认为,ML和OCaml非常适合编写编译器,因为它们具有垃圾收集、尾递归优化以及带有模式匹配的代数数据类型等特性,这些特性简化了复杂编译器数据结构的处理。
@aryanvs_: 在外行人看来,这也许只是噪音。但这里蕴藏着数月来编写编译器的努力,最好的部分…
一位开发者分享了数月来构建一个编译器的成果,该编译器在A100 GPU上的矩阵乘法性能超越了cuBLAS,并附带了可视化效果。
优化CPU密集型Go热路径的笔记
本文讨论了CPU密集型Go代码的性能优化技术,指出了泛型和接口抽象因无法内联而产生的局限性,并主张在热路径中使用代码复制。文章通过一个Brotli移植示例和深入基准测试进行了说明。