C++ 解释GCC编译器优化';什么是不利的绩效影响?
请注意:这个问题既不是关于代码质量和改进代码的方法,也不是关于运行时差异的重要性。这是关于GCC以及为什么哪种编译器优化会影响性能 节目 以下代码统计最多为C++ 解释GCC编译器优化';什么是不利的绩效影响?,c++,performance,gcc,compiler-optimization,C++,Performance,Gcc,Compiler Optimization,请注意:这个问题既不是关于代码质量和改进代码的方法,也不是关于运行时差异的重要性。这是关于GCC以及为什么哪种编译器优化会影响性能 节目 以下代码统计最多为m的斐波那契素数: intmain(){ 无符号整数m=500000000u; 无符号整数i=0u; 无符号整数a=1u; 无符号整数b=1u; 无符号整数c=1u; 无符号整数计数=0u; 而(a+b我认为这是由编译器在使用-O1和-O2时生成的“条件移动”指令(CMOVEcc)造成的 使用-O0时,如果(c==0u)语句被编译为跳转:
m
的斐波那契素数:
intmain(){
无符号整数m=500000000u;
无符号整数i=0u;
无符号整数a=1u;
无符号整数b=1u;
无符号整数c=1u;
无符号整数计数=0u;
而(a+b我认为这是由编译器在使用-O1
和-O2
时生成的“条件移动”指令(CMOVEcc)造成的
使用-O0
时,如果(c==0u)
语句被编译为跳转:
cmp DWORD PTR [rbp-16], 0
jne .L4
使用-O1
和-O2
:
test edx, edx
cmove ecx, esi
而-O3
产生跳转(类似于-O0
):
其中“使用条件移动而不是比较和分支会导致代码速度几乎降低2倍”
正如rodrigo在他的评论中所建议的那样,如果转换使用标志-fno告诉gcc不要用条件移动替换分支,因此防止了这个性能问题。我认为问题在于
-fif转换
,它指示编译器执行CMOV
,而不是TEST/JZ和CMOV
在一般情况下不是很好
据我所知,反汇编中有两点受此标志的影响:
首先,将第13行中的if(c==0u){i=a+b;}
编译为:
test edx,edx //edx is c
cmove ecx,esi //esi is (a + b), ecx is i
其次,如果(c!=0u){count=count+1u;}编译为
cmp eax,0x1 //eax is c
sbb r8d,0xffffffff //r8d is count, but what???
很好的技巧!它将-1
减去计数
,但使用进位,并且只有c
小于1
时才设置进位,无符号意味着0
。因此,如果eax
为0,它将-1减去计数
,然后再次减去1:它不会改变。如果eax
不改变0,然后减去-1
,从而增加变量
现在,这避免了分支,但代价是错过了明显的优化,如果
c==0u
,您可以在迭代时直接跳到下一个。这一步非常简单,甚至可以在-O0
中完成。这里重要的事情是循环携带的数据依赖关系
看看最里面循环的慢速变体的机器代码。我在这里展示了-O2
程序集,-O1
的优化程度较低,但总体上具有类似的数据依赖性:
.L4:
xorl %edx, %edx
movl %esi, %eax
divl %ecx
testl %edx, %edx
cmove %esi, %ecx
addl $1, %ecx
cmpl %ecx, %esi
ja .L4
查看%ecx
中循环计数器的增量如何取决于上一条指令(cmov
),这又取决于除法结果,而除法结果又取决于循环计数器的上一个值
实际上,在计算跨越整个循环的%ecx
中的值时存在一个数据依赖链,由于执行循环的时间占主导地位,因此计算该链的时间决定了程序的执行时间
调整程序以计算分割数表明它执行434044698div
指令。在我的例子中,将程序所占用的机器周期数除以该数字得到26个周期,这与div
指令的延迟加上另一条指令的大约3或4个周期非常接近链中的离子(链为div
-test
-cmov
-add
)
相比之下,-O3
代码没有这种依赖链,因此它是吞吐量范围而不是延迟范围:执行-O3
变量的时间由计算434044698独立div
指令的时间决定
最后,为您的问题提供具体答案:
1.哪种优化能够/能够解释性能下降?
正如另一个答案所提到的,这是如果转换创建了一个循环携带的数据依赖项,而最初有一个控制依赖项。当控制依赖项对应于不可预测的分支时,它们可能也很昂贵,但在这种情况下,分支很容易预测
<>>2。有可能解释什么触发了C++代码的优化(即没有对GCC内部结构的深刻理解)?<强> < /P>
也许您可以想象优化将代码转换为
(i=2u;i{
c=(a+b)%i;
i=(c!=0)?i:a+b;
}
其中,在CPU上对三值运算符进行求值,从而在计算出c
之前,不知道i
的新值
3.类似地,对于为什么备选方案(额外观察)明显阻止恶意优化,是否有一个高层次的解释?
在这些变体中,代码不符合if转换的条件,因此没有引入有问题的数据依赖关系。我认为这可能与
I
中可能存在的溢出有关。请注意,当您想要结束循环时,您执行I=a+b;
但是增量发生在循环条件之前。如果a呢+b==MAX_UINT
?如果将++i
移动到else
或即使将i
控制变量添加到循环中,也会恢复丢失的性能。如果转换测试.c返回良好性能,则使用gcc-O1-fno编译。不知道为什么…检查两个opti的程序集输出正如你们所看到的,ons优化版本生成的CPU指令更少,而这些指令对于当前任务更为优化
.L4:
xorl %edx, %edx
movl %esi, %eax
divl %ecx
testl %edx, %edx
cmove %esi, %ecx
addl $1, %ecx
cmpl %ecx, %esi
ja .L4