我在llvm clang Apple llvm version8.0.0(clang-800.0.42.1)上反汇编此代码:
int main() {
float a=0.151234;
float b=0.2;
float c=a+b;
printf("%f", c);
}
我使用no-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)
...
-O0
(未优化)是默认值。它告诉编译器,您希望它快速编译(短编译时间),而不是花费额外的时间编译以生成高效的代码。
(-O0
并不完全是优化;例如,如果(1==2){}块,gcc仍然会消除中的代码。特别是gcc比大多数其他编译器仍然会在
-O0
中使用乘法逆运算进行除法,因为在最终发出ASM之前,它仍然会通过逻辑的多个内部表示来转换您的C源代码。)
此外,“编译器总是正确的”甚至在
-O3
中也是夸大其词。编译器在大规模上非常出色,但是在单个循环中仍然很常见的是轻微的漏掉优化。通常影响很小,但循环中浪费的指令(或UOP)会占用乱序执行重排序窗口中的空间,并且在与另一个线程共享内核时对超线程不太友好。请参阅C++代码,以测试比手写程序集更快的Collatz猜想--为什么?有关在一个简单的特定情况下击败编译器的更多信息。
更重要的是,
-O0
还意味着要处理与volatile
类似的所有变量,以便进行一致的调试。也就是说,您可以设置一个断点或单个步骤,修改一个C变量的值,然后继续执行,使程序按照您在C抽象机器上运行的C源代码所期望的方式工作。因此编译器不能进行任何常数传播或值范围简化。(例如,一个已知为非负的整数可以简化使用它的事情,或者使一些if条件总是为真或总是为假。)
(这并不像
volatile
那么糟糕:在一个语句中对同一个变量的多个引用并不总是导致多个加载;在-O0
中,编译器仍然会在单个表达式中进行一些优化。)
编译器必须对
-O0
进行专门的反优化,将所有变量存储/重新加载到语句之间的内存地址中。(在C和C++中,每个变量都有一个地址,除非它是用(现在已经过时的)register
关键字声明的,而且从来没有取过它的地址。根据其他变量的as-if规则,优化地址是可能的,但在-o0
中没有这样做)
不幸的是,debug-info格式不能通过寄存器跟踪变量的位置,因此如果没有这种缓慢而愚蠢的代码生成,完全一致的调试是不可能的。
如果不需要,可以使用
-og
进行编译,以进行轻度优化,而无需进行一致调试所需的反优化。GCC手册建议将其用于通常的编辑/编译/运行周期,但在调试时,您将得到许多具有自动存储的局部变量的“优化”。全局和函数arg通常仍有其实际值,至少在函数边界处是如此。
更糟糕的是,
-O0
生成的代码即使使用GDB的jump
命令在不同的源代码行继续执行也仍然有效。因此,每个C语句都必须编译成一个完全独立的指令块。(在GDB调试器中可以“跳”/“跳”吗?)
由于上述所有原因,(微观)对未优化的代码进行基准测试是对时间的巨大浪费;结果取决于如何编写源代码的愚蠢细节,在使用常规优化进行编译时,这些细节并不重要。
-O0
与-O3
性能不是线性相关的;有些代码的速度会比其他代码快得多。
-O0
代码中的瓶颈通常与-O3
不同--通常是在内存中保存的循环计数器上,创建一个~6个循环的循环携带依赖链。这可以在编译器生成的asm中创建有趣的效果,比如添加冗余赋值可以在未经优化的情况下编译代码(从asm的角度来看,这很有趣,但对于C来说并非如此)
“我的基准优化了,否则”不是查看
-O0
代码性能的有效理由。有关优化-O0
的兔子洞的示例和更多细节,请参见C循环优化帮助中的最终赋值。
float foo(float a, float b) {
float c=a+b;
return c;
}
使用
clang-o3
(在Godbolt编译器资源管理器上)编译为预期的
addss xmm0, xmm1
ret
但是对于
-o0
,它会将arg溢出到堆栈内存中。(Godbolt使用编译器发出的调试信息对asm指令进行颜色编码,根据它们来自哪个C语句。我添加了换行符来显示每个语句的块,但您可以在上面的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
null
我非常困惑为什么gcc会为const数组上的简单for循环生成这种(看似)非最佳代码。 结果: 我主要关心的是: 为什么无用的第一个元素比较在?这永远不会命中,也永远不会被分支回。它最终只是第一次迭代的重复代码。 < li >有没有更好的方法来编写这个非常简单的循环,这样gcc就不会产生这种奇怪的代码? < li >有没有我可以利用的编译器标志/优化?< code>O3只是展开循环,我也不希望这样
在这个打印从1到10000000的所有数字、Haskell版本和C版本的简单程序中,为什么Haskell版本如此缓慢,以及哪些命令有助于学习如何提高Haskell程序的性能? 下面是一份报告,包含重现我激动人心的事件所需的所有细节,制作报告时会打印出来源,包括Makefile的来源:
我无法理解的是,为什么当我使用Eclipse将所有依赖项提取到jar中的文件夹结构中时,jar不再作为一个正确的Spring Boot应用程序运行。 为了重复我在这里所做的事情,只需使用一个简单的Spring Boot应用程序,并从Eclipse中选择以下内容: 导出-->Runnable JAR-->选择Main Class-->将所需库提取到Jar中。 我只是想了解Spring实际上是如何通过
我在学校上课: 然后我创建了School的两个实例,并比较了两个实例的相等性: 即使我设置了相同的和到
所以浮点运算是不精确的,但这并不能完全解释这里发生的事情:
问题内容: 很简单的一行: 失败与: 而扩展为: 工作良好。 问题答案: 您使用错误。使用这种方式: 通用形式为: