Backstory

考虑一下不同代码写法会不会影响性能?例如:

写法 A

1
2
3
4
5
6
7
8
9
int sum(const std::vector<int> &v)
{
int result = 0;
for(size_t i = 0; i < v.size(); ++i)
{
result += v[i];
}
return result;
}

写法 B

1
2
3
4
5
6
7
8
9
int sum(const std::vector<int> &v)
{
int result = 0;
for(int x : v)
{
result += x;
}
return result;
}

这两种写法在功能上等效。但如果想真正确认哪一种在性能上取胜,则需要使用基准测试 (benchmark)。在这里,写法 B 的性能更好。

与此同时,我们还想知道自己能否承担这种性能提升的代价,这时需要查看编译器输出的汇编结果了。

Warning

不要只看汇编结果。我们看不完那么多汇编指令,而且这还是经过编译器优化修改过的产物。如果需要比较,请一并做好基准测试。

x86_64 Assembly 101

寄存器

x86_64 架构下的寄存器有:

  • rax, rbx, rcx, rdx, rsp, rbp, rsi, rdi, r8-r15
  • xmm0-xmm15
  • rdi, rsi, rdx 等用来存储变量
  • rax 用于存储返回值
64 位的数存进了 32 位的 eax

在 x86_64 架构中,rax 的长度是 64 位,eax 的长度是 32 位。当数据写入 eax 中时,实际上是将 rax 的高位的 32 个位清 0,仅在低位的 32 个位写入数据。这是一个寄存器相关的技巧。

指令格式

我们使用 Intel 语法格式:

1
2
3
4
op
op dest
op dest, src
op dest, src1, src2

这里的 op 指代任意汇编指令,比如 mov, calldestsrc 是寄存器或者内存指针,使用 “基址 + 偏移” 表示,即 base + [reg1] + [reg2 * (1, 2, 4 or 8)]

汇编指令与伪 C 代码的对照

汇编

1
2
3
4
5
6
mov eax, DWORD PTR [r14]
add rax, rdi
add eax, DWORD PTR [r14+4]
sub eax, DWORD PTR [r14+4*rbx]
lea rax, [r14+4*rbx]
xor edx, edx

伪 C 代码

1
2
3
4
5
6
int eax = *r14 // int *r14;
rax += rdi;
eax += r14[1];
eax -= r14[rbx];
int *rax = &r14[rbx];
edx = 0;
如何清 0 寄存器?

有人会将 edx = 0 肉编译为 mov edx, 0,但这里却是用 xor edx, edx。有两个原因:1)mov 中使用的 0 必须以 4 字节存储,体积要大得多;2)指令集已经明白 xor 有清 0 的作用,因此进行对应优化 —— 换句话说,xor 更偏向 “重置”。

Compiler Explorer

最初的 Compiler Explorer 是一个本地 shell 脚本,主要功能部分长这样:

1
2
3
g++ /tmp/test.cc -O2 -c -S -o --masm=intel \
| c++filt \
| grep -vE '\s+\.'

然后用 Unix 工具 watch 持续运行脚本,得到近实时编译结果:

1
2
3
4
5
6
7
8
sum(std::vector<int, std::allocator<int> > const&):
.LFB786:
mov rcx, QWORD PTR [rdi]
mov rax, QWORD PTR 8[rdi]
sub rax, rcx
shr rax, 2
mov rsi, rax
; ...

后面部署到线上,有了 GUI,可以在 Compiler Explorer 上探索。

通过示例,我们可以清晰地看到写法 A 和写法 B 的汇编区别:写法 B 的汇编明显更短。

Warning

本文的示例使用 x86-64 gcc 15.1 -O2 编译这些函数,而原作者使用 x86-64 gcc 8.1 编译,得到的结果会有差异。

通过引入 numeric 库,我们还有写法 C:

1
2
3
4
int sum(const std::vector<int> &v)
{
return accumulate(begin(v), end(v), 0);
}

写法 C 的汇编几乎和 B 相同,只有两个指令的差异 —— 不过是位置的变化罢了。


©2025-Present Watermelonabc | 萌 ICP 备 20251229 号

Powered by Hexo & Stellar 1.33.1 & Vercel & HUAWEI Cloud
您的访问数据将由 Vercel 和自托管的 Umami 进行隐私优先分析,以优化未来的访问体验

本博客总访问量:capoo-2

| 开往-友链接力 | 异次元之旅

中文独立博客列表 | 博客录 随机博客

AI 参与指数(IIIA)2 级

猫猫🐱 发表了 55 篇文章 · 总计 229.3k 字