Pyv*_*ves 9 c++ gcc compiler-optimization loop-unrolling
这个问题部分是GCC 5.1循环展开的后续问题.
根据GCC文档,并且如我在上述问题的答案中所述,标志如-funroll-loops打开"完全循环剥离(即完全去除具有少量恒定迭代次数的循环)".因此,当启用这样的标志时,如果确定这将优化给定代码段的执行,则编译器可以选择展开循环.
尽管如此,我注意到在我的一个项目中,即使没有启用相关标志,GCC有时会展开循环.例如,考虑以下简单的代码:
int main(int argc, char **argv)
{
int k = 0;
for( k = 0; k < 5; ++k )
{
volatile int temp = k;
}
}
Run Code Online (Sandbox Code Playgroud)
在编译时-O1,循环展开,并使用任何现代版本的GCC生成以下汇编代码:
main:
movl $0, -4(%rsp)
movl $1, -4(%rsp)
movl $2, -4(%rsp)
movl $3, -4(%rsp)
movl $4, -4(%rsp)
movl $0, %eax
ret
Run Code Online (Sandbox Code Playgroud)
即使在使用附加-fno-unroll-loops -fno-peel-loops程序进行编译以确保禁用标志时,GCC仍然意外地仍在上述示例中执行循环展开.
这一观察引出了以下密切相关的问题.为什么GCC执行循环展开,即使禁用了与此行为相对应的标志?展开是否也受其他标志控制,这些标志可以使编译器在某些情况下展开循环,即使-funroll-loops被禁用?有没有办法完全禁用GCC中的循环展开(编译的一部分-O0)?
有趣的是,Clang编译器在此处具有预期的行为,并且似乎仅在-funroll-loops启用时执行展开,而在其他情况下不执行.
在此先感谢您对此事的任何其他见解将不胜感激!
为什么GCC执行循环展开,即使禁用了与此行为相对应的标志?
从实用的角度来看:将这样的标志传递给编译器时你想要什么?没有C++开发人员会要求GCC展开或不展开循环,只是为了在汇编代码中有循环,有一个目标.-fno-unroll-loops例如,如果您正在开发具有有限存储的嵌入式软件,那么目标就是牺牲一点速度以减小二进制文件的大小.另一方面,目标-funrool-loops是告诉编译器你不关心二进制文件的大小,所以它应该毫不犹豫地展开循环.
但这并不意味着编译器会盲目地展开或不是所有的循环!
在你的例子中,原因很简单:循环只包含一条指令 - 任何平台上的几个字节 - 并且编译器知道这是可以忽略的,并且无论如何都将采用与循环所需的汇编代码几乎相同的大小(sub+ mov+ jneon X86-64).
这就是为什么gcc 6.2,-O3 -fno-unroll-loops转而使用这段代码:
int mul(int k, int j)
{
for (int i = 0; i < 5; ++i)
volatile int k = j;
return k;
}
Run Code Online (Sandbox Code Playgroud)
...到以下汇编代码:
mul(int, int):
mov DWORD PTR [rsp-0x4],esi
mov eax,edi
mov DWORD PTR [rsp-0x4],esi
mov DWORD PTR [rsp-0x4],esi
mov DWORD PTR [rsp-0x4],esi
mov DWORD PTR [rsp-0x4],esi
ret
Run Code Online (Sandbox Code Playgroud)
它不会听你的,因为它(几乎,取决于架构)不会改变二进制文件的大小,但它更快.但是,如果你增加一点你的循环计数器......
int mul(int k, int j)
{
for (int i = 0; i < 20; ++i)
volatile int k = j;
return k;
}
Run Code Online (Sandbox Code Playgroud)
...它遵循你的提示:
mul(int, int):
mov eax,edi
mov edx,0x14
nop WORD PTR [rax+rax*1+0x0]
sub edx,0x1
mov DWORD PTR [rsp-0x4],esi
jne 400520 <mul(int, int)+0x10>
repz ret
Run Code Online (Sandbox Code Playgroud)
如果你保持循环计数器5但是你在循环中添加了一些代码,你会得到相同的行为.
总而言之,将所有这些优化标志视为编译器的提示,并从实用的开发人员的角度出发.它始终是一种权衡,当您构建软件时,您永远不会要求全部或不需要循环展开.
最后一点,另一个非常相似的例子就是-f(no-)inline-functions旗帜.我每天都在为编译器内联(或不是!)我的一些函数(使用inline关键字和__attribute__ ((noinline))GCC)而战斗,当我检查汇编代码时,我看到这个smartass有时仍在做它想要的东西,当我想要内联一个绝对太长的功能.大多数时候,这是正确的事情,我很高兴!