C 当限制为959而不是960时,为什么要优化简单循环?
考虑这个简单的循环:C 当限制为959而不是960时,为什么要优化简单循环?,c,gcc,optimization,clang,C,Gcc,Optimization,Clang,考虑这个简单的循环: float f(float x[]) { float p = 1.0; for (int i = 0; i < 959; i++) p += 1; return p; } 换句话说,它只是将答案设置为960而没有循环 但是,如果将代码更改为: float f(float x[]) { float p = 1.0; for (int i = 0; i < 960; i++) p += 1; return p; } 为什么
float f(float x[]) {
float p = 1.0;
for (int i = 0; i < 959; i++)
p += 1;
return p;
}
换句话说,它只是将答案设置为960而没有循环
但是,如果将代码更改为:
float f(float x[]) {
float p = 1.0;
for (int i = 0; i < 960; i++)
p += 1;
return p;
}
为什么这和为什么clang和gcc完全一样
如果将
float
替换为double
则相同循环的限制为479。gcc和clang也是如此
更新1
原来GCC7(快照)和clang(主干)的行为非常不同。就我所知,clang优化了所有小于960的限制的循环。另一方面,gcc对精确值很敏感,没有上限。例如,当限制为200(以及许多其他值)时,它不会优化循环,但当限制为202和20002(以及许多其他值)时,它会优化循环。阅读Sulthan的评论后,我猜:
1000
的语句),则无法对操作进行分组
编译器可以看到,1000条语句的展开相当于一次加法,但上面描述的步骤1和2是两个单独的优化,因此它不能承担展开的“风险”,因为不知道操作是否可以分组(例如:函数调用不能分组)
注意:这是一个极端情况:谁使用循环再次添加相同的内容?在这种情况下,不要依赖编译器可能的展开/优化;直接在一条指令中写入正确的操作。TL;博士
默认情况下,当前快照GCC 7的行为不一致,而早期版本的默认限制为16。可以从命令行重写它
该限制的基本原理是防止过于激进的循环展开,这可能是一个错误
GCC版本非常好的问题
在简化代码时,编译器尝试内联的迭代次数或操作次数似乎达到了限制。正如Grzegorz Szpetkowski所记录的,有一些特定于编译器的方法可以使用pragmas或命令行选项来调整这些限制
您还可以使用来比较不同的编译器和选项如何影响生成的代码:
gcc 6.2
和icc 17
仍然内联960的代码,而clang 3.9
没有(使用默认的Godbolt配置,它实际上在73处停止内联).Sulthan的意思可能是:1)编译器展开循环,2)一旦展开,就可以将求和操作分组为一个。如果循环未展开,则无法对操作进行分组。循环数为奇数会使展开更加复杂,最后几次迭代必须专门进行。这可能足以让优化器进入无法识别快捷方式的模式。很可能,它首先必须为特殊情况添加代码,然后再删除它。在耳朵之间使用优化器总是最好的:)@HansPassant它也针对任何小于959的数字进行了优化。这不是通常通过归纳变量消除来完成,而不是展开一个疯狂的数量吗?按959的倍数展开是疯狂的。@eleanora我用那个编译器资源管理器玩过,下面的说法似乎成立(仅讨论gcc快照):如果循环计数是4的倍数,并且至少是72,那么循环就不会展开(或者说,按4的倍数展开);否则,整个循环将替换为一个常量-即使循环计数为20000000001。我的怀疑是:过早的优化(比如说,过早的“嘿,4的倍数,这有利于展开”这一块进一步的优化,而不是更彻底的“这个循环到底是怎么回事?”),那么你能专注于不太高的部分吗?我的意思是为什么在100
的情况下没有风险?我在上面的评论中猜到了一些东西。这可能是原因吗?我认为编译器没有意识到它可能触发的浮点错误。我想这只是指令大小的限制。您有max unrolled insns
和max unrolled times
啊,这是我的想法或猜测……希望得到更清晰的推理。有趣的是,如果您将float
更改为int
,gcc编译器能够在不考虑迭代次数的情况下降低循环强度,由于其感应变量优化(-fivopts
)。但这些似乎不适用于float
s.@CortAmmon,对吧,我记得读到一些人感到惊讶和不安的是,GCC使用MPFR精确计算非常大的数字,给出的结果与等效的浮点运算截然不同,后者会累积错误和精度损失。这说明很多人计算浮点的方法是错误的。我对这个问题进行了编辑,以澄清我使用的gcc和clang的版本。看见我用的是Ofast,谢谢你的回答。正如其他人所指出的,gcc似乎对确切的限制大小很敏感。例如,它无法消除912的循环。在这种情况下,fdump tree cunroll细节说明了什么?事实上,即使是200也有这个问题。这都是godbolt提供的gcc 7的快照。这根本不适用于叮当声。你解释了剥落的机理,但没有解释960的相关性,或者为什么它有一个极限all@M.M:GCC 6.3.0和最新的snaphost之间的剥离行为完全不同。对于前者,我强烈怀疑,硬编码限制是由PARAM_MAX_completed_PEEL_TIMES
PARAM,t执行的
float f(float x[]) {
float p = 1.0;
for (int i = 0; i < 960; i++)
p += 1;
return p;
}
.LCPI0_0:
.long 1065353216 # float 1
.LCPI0_1:
.long 1086324736 # float 6
f: # @f
vmovss xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
vxorps ymm1, ymm1, ymm1
mov eax, 960
vbroadcastss ymm2, dword ptr [rip + .LCPI0_1]
vxorps ymm3, ymm3, ymm3
vxorps ymm4, ymm4, ymm4
.LBB0_1: # =>This Inner Loop Header: Depth=1
vaddps ymm0, ymm0, ymm2
vaddps ymm1, ymm1, ymm2
vaddps ymm3, ymm3, ymm2
vaddps ymm4, ymm4, ymm2
add eax, -192
jne .LBB0_1
vaddps ymm0, ymm1, ymm0
vaddps ymm0, ymm3, ymm0
vaddps ymm0, ymm4, ymm0
vextractf128 xmm1, ymm0, 1
vaddps ymm0, ymm0, ymm1
vpermilpd xmm1, xmm0, 1 # xmm1 = xmm0[1,0]
vaddps ymm0, ymm0, ymm1
vhaddps ymm0, ymm0, ymm0
vzeroupper
ret
$ head test.c.151t.cunroll
;; Function f (f, funcdef_no=0, decl_uid=1919, cgraph_uid=0, symbol_order=0)
Not peeling: upper bound is known so can unroll completely
if (maxiter >= 0 && maxiter <= npeel)
{
if (dump_file)
fprintf (dump_file, "Not peeling: upper bound is known so can "
"unroll completely\n");
return false;
}
Loop 1 iterates 959 times.
Loop 1 iterates at most 959 times.
Not unrolling loop 1 (--param max-completely-peeled-times limit reached).
Not peeling: upper bound is known so can unroll completely
max-completely-peeled-insns
max-completely-peel-times
-march=core-avx2 -Ofast --param max-completely-peeled-insns=1000 --param max-completely-peel-times=1000
f:
vmovss xmm0, DWORD PTR .LC0[rip]
ret
.LC0:
.long 1148207104
#pragma unroll
for (int i = 0; i < 960; i++)
p++;
.LCPI0_0:
.long 1148207104 # float 961
f: # @f
vmovss xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
ret