当编译器让你惊喜
摘要
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。
相似文章
使用并行Claude团队构建C编译器
Anthropic研究员展示了如何使用16个并行Claude实例自主构建一个基于Rust的C编译器,该编译器能够编译Linux内核。文章详细介绍了这一多智能体自主编码实验的架构、成本和经验教训。
用 Zig 写一个 C 编译器
一位开发者记录了用 Zig 语言、按照 Nora Sandler 的教程系列构建名为 paella 的 C 编译器的全过程。
让 Julia 达到 C++ 的速度(2019)
这是 BYU FLOW Lab 于 2019 年发布的一篇博客文章,以真实的空气动力学应用(涡粒子法)作为基准测试,探讨如何优化 Julia 代码以匹配 C++ 的性能。作者分享了在 Julia 中实现高性能计算的经验,涵盖类型声明、JIT 编译以及代码优化技巧。
@agupta:有些想法在用编码智能体做出概念验证后会清晰得多,例如我直到看了这篇附代码的文章才真正明白 GPU 与 NPU 在设备上如何竞争内存……
一条推文指出,编码智能体能帮助阐明复杂概念,并以 GPU 与 NPU 在设备上的内存竞争为例,通过代码进行了演示。
本地LLM实战测试:代码生成、质量与速度权衡
作者构建了一个基准测试框架,用于评估本地LLM在自动生成Go代码方面的能力,重点聚焦SIEM流水线的日志解析器生成,并发布了对比质量与速度的测试结果。