【发布时间】:2020-06-16 10:54:42
【问题描述】:
我在 llvm clang Apple LLVM 版本 8.0.0 (clang-800.0.42.1) 上反汇编这段代码:
int main() {
float a=0.151234;
float b=0.2;
float c=a+b;
printf("%f", c);
}
我编译时没有使用 -O 规范,但我也尝试使用 -O0(给出相同的值)和 -O2(实际计算值并将其存储为预先计算)
得到的反汇编如下(我去掉了不相关的部分)
-> 0x100000f30 <+0>: pushq %rbp
0x100000f31 <+1>: movq %rsp, %rbp
0x100000f34 <+4>: subq $0x10, %rsp
0x100000f38 <+8>: leaq 0x6d(%rip), %rdi
0x100000f3f <+15>: movss 0x5d(%rip), %xmm0
0x100000f47 <+23>: movss 0x59(%rip), %xmm1
0x100000f4f <+31>: movss %xmm1, -0x4(%rbp)
0x100000f54 <+36>: movss %xmm0, -0x8(%rbp)
0x100000f59 <+41>: movss -0x4(%rbp), %xmm0
0x100000f5e <+46>: addss -0x8(%rbp), %xmm0
0x100000f63 <+51>: movss %xmm0, -0xc(%rbp)
...
显然它正在执行以下操作:
- 将两个浮点数加载到寄存器 xmm0 和 xmm1
- 将它们放入堆栈
- 从堆栈中加载一个值(不是 xmm0 之前的那个)到 xmm0
- 执行添加。
- 将结果存储回堆栈。
我觉得它效率低下,因为:
- 一切都可以在注册表中完成。我以后不使用 a 和 b,所以它可以跳过任何涉及堆栈的操作。
- 即使它想使用堆栈,如果它以不同的顺序执行操作,它也可以节省从堆栈中重新加载 xmm0。
鉴于编译器永远是对的,为什么它会选择这种策略?
【问题讨论】:
-
因为您没有启用优化,这是最简单的方法。
-
尽管基本答案很简单,但感谢您编写这个格式良好的问题。有一些有趣的事情要说,这看起来是放置我经常作为其他答案的一部分重复的规范答案的好地方。现在我可以将其链接为
-O0的首选,因为它是查看编译器生成的 asm 的错误选择,而-O0对 asm 的含义正是如此。 -
不要试图通过查看 asm/c 代码来预测执行时间,现代 CPU 极其复杂的黑匣子,如果你不是专家,你很容易就错了。 CPU 以不同的速度、流水线、数据依赖性、超标量无序执行指令 - 所有这些东西都可以比较短和明显的更快地运行较长的虚拟程序。这是一般规则,总是运行,不要看代码。
标签: c assembly x86-64 compiler-optimization llvm-codegen