-O0
(未优化)是默认值。它告诉编译器您希望它快速编译(编译时间短),而不是花费额外的时间编译以生成高效的代码。
(-O0
并不是字面上没有优化;例如 gcc 仍然会消除if(1 == 2){ }
块内的代码。特别是 gcc 比大多数其他编译器仍然会使用乘法逆进行除法之类的事情-O0
,因为它仍然通过之前逻辑的多个内部表示来转换您的 C 源代码最终发出 asm。)
另外,“编译器永远是对的”即使在-O3
. 编译器在大规模方面非常出色,但在单个循环中仍然很常见轻微的错过优化。通常影响非常低,但循环中浪费的指令(或微指令)会占用乱序执行重新排序窗口中的空间,并且在与另一个线程共享内核时对超线程不太友好。请参阅C++ 代码以比手写汇编更快地测试 Collatz 猜想 - 为什么?有关在简单的特定情况下击败编译器的更多信息。
更重要的是,-O0
还意味着将所有变量都视为类似以volatile
进行一致的调试。也就是说,您可以设置断点或单步并修改C 变量的值,然后继续执行并让程序按照您在 C 抽象机器上运行的 C 源代码所期望的方式工作。所以编译器不能做任何常量传播或值范围简化。(例如,已知为非负的整数可以使用它来简化事情,或者使某些条件始终为真或始终为假。)
(它并没有那么糟糕volatile
:在一个语句中对同一个变量的多次引用并不总是导致多次加载;at-O0
编译器仍然会在单个表达式中进行一些优化。)
-O0
编译器必须通过在语句之间将所有变量存储/重新加载到它们的内存地址来专门进行反优化。(在 C 和 C++ 中,每个变量都有一个地址,除非它是用(现在已过时的)register
关键字声明的并且从未使用过它的地址。根据其他变量的 as-if 规则优化地址是可能的,但不是t 完成-O0
)
不幸的是,调试信息格式无法通过寄存器跟踪变量的位置,因此如果没有这种缓慢而愚蠢的代码生成,就不可能进行完全一致的调试。
如果你不需要这个,你可以编译以-Og
进行轻度优化,而无需进行一致调试所需的反优化。GCC 手册建议将它用于通常的编辑/编译/运行周期,但在调试时,您将针对许多具有自动存储功能的局部变量“优化”。全局变量和函数参数通常仍然具有它们的实际值,至少在函数边界处是这样。
更糟糕的是,-O0
即使您使用 GDB 的jump
命令在不同的源代码行继续执行,代码仍然可以工作。因此,每个 C 语句都必须编译成完全独立的指令块。(是否可以在 GDB 调试器中“跳转”/“跳过”?)
for()
循环不能转换为惯用的(对于 asm)do{}while()
循环和其他限制。
由于上述所有原因,(微)基准测试未优化的代码是浪费时间。结果取决于您编写源代码的愚蠢细节,当您使用正常优化进行编译时,这些细节并不重要。 -O0
与-O3
性能不是线性相关的;有些代码会比其他代码加速得多。
代码中的瓶颈-O0
通常不同于-O3
- 通常在保存在内存中的循环计数器上,创建一个 ~6 循环循环携带的依赖链。这可以在编译器生成的 asm 中产生有趣的效果,例如 在没有优化的情况下编译时添加冗余赋值会加速代码(从 asm 的角度来看这很有趣,但对于 C语言却不是。)
“否则我的基准优化了”并不是查看-O0
代码性能的有效理由。有关最终分配的示例和有关调整的兔子洞的更多详细信息,请参阅C 循环优化帮助-O0
。
获得有趣的编译器输出
如果您想查看编译器如何添加 2 个变量,请编写一个接受 args 并返回 value 的函数。main
请记住,您只想查看 asm,而不是运行它,因此对于应该是运行时变量的任何内容,您不需要 a或任何数字文字值。
另请参阅如何从 GCC/clang 程序集输出中删除“噪音”?有关此的更多信息。
float foo(float a, float b) {
float c=a+b;
return c;
}
用clang -O3
(在 Godbolt 编译器资源管理器上)编译到预期的
addss xmm0, xmm1
ret
但是-O0
它会将args溢出到堆栈内存中。(Godbolt 使用编译器发出的调试信息来根据它们来自哪个 C 语句对 asm 指令进行颜色编码。我添加了换行符以显示每个语句的块,但是您可以在上面的 Godbolt 链接上通过颜色突出显示来看到这一点. 在优化的编译器输出中查找内部循环的有趣部分通常非常方便。)
gcc -fverbose-asm
将在将操作数名称显示为 C 变量的每一行上添加注释。在通常是内部 tmp 名称的优化代码中,但在未优化的代码中,它通常是来自 C 源代码的实际变量。我已经手动注释了 clang 输出,因为它没有这样做。
# clang7.0 -O0 also on Godbolt
foo:
push rbp
mov rbp, rsp # make a traditional stack frame
movss DWORD PTR [rbp-20], xmm0 # spill the register args
movss DWORD PTR [rbp-24], xmm1 # into the red zone (below RSP)
movss xmm0, DWORD PTR [rbp-20] # a
addss xmm0, DWORD PTR [rbp-24] # +b
movss DWORD PTR [rbp-4], xmm0 # store c
movss xmm0, DWORD PTR [rbp-4] # return 0
pop rbp # epilogue
ret
有趣的事实:使用register float c = a+b;
,返回值可以在语句之间保留在 XMM0 中,而不是被溢出/重新加载。该变量没有地址。(我在 Godbolt 链接中包含了该版本的函数。)
该register
关键字在优化代码中没有任何影响(除了使获取变量的地址成为错误,例如const
在本地如何阻止您意外修改某些内容)。我不推荐使用它,但有趣的是它确实会影响未优化的代码。
有关的: