2016-09-13 171 views
9

此问题部分是GCC 5.1 Loop unrolling的后续问题。GCC中的循环展开行为

按照GCC documentation,并且如在我的回答表示对上述问题,标志,如-funroll-loops接通“完整循环剥离(即完全除去环的具有小恒定数目的迭代)”。因此,如果启用这样的标志,编译器可以选择展开循环,如果它确定这将优化给定代码段的执行。

尽管如此,我注意到在我的一个项目中,即使相关标志未启用,GCC有时也会展开循环。例如,考虑下面的一段简单的代码:

int main(int argc, char **argv) 
{ 
    int k = 0; 
    for(k = 0; k < 5; ++k) 
    { 
    volatile int temp = k; 
    } 
} 

-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 

即使与附加-fno-unroll-loops -fno-peel-loops编译,以确保标志是禁用,海湾合作委员会意外仍然执行循环展开上述例子。

这个观察将我引向以下密切相关的问题。为什么GCC执行循环展开,即使与此行为相对应的标志被禁用?展开也受其他标志控制,即使-funroll-loops被禁用,可以使编译器在某些情况下展开循环。有没有办法完全禁用GCC中的循环展开(从编译-O0开始)?

有趣的是,编译器在这里有预期的行为,并似乎在-funroll-loops被启用,而不是在其他情况下,仅执行展开。

在此先感谢,有关此事的任何其他见解将不胜感激!

+0

恭喜。你发现不同的编译器在行为上有所不同,你传给他们的标志并不总是表示你认为他们的意思。欢迎来到真实的世界。 –

+0

它会破坏你程序的功能吗? – Serge

+0

不,它不会破坏功能。对于GCC如何执行循环展开以及如何调整这种行为,这更是一个普遍关心的问题。 – Pyves

回答

7

为什么GCC执行循环展开,即使与此行为相对应的标志 被禁用?

从实用的角度思考它:将这样的标志传递给编译器时你想要什么?没有C++开发人员会要求GCC展开或不展开循环,只是为了循环或不汇编代码,有一个目标。例如,如果您正在开发具有有限存储的嵌入式软件,那么为了减小二进制文件的大小,例如,要牺牲一点速度,-fno-unroll-loops。另一方面,-funrool-loops的目标是告诉编译器,你不关心你的二进制文件的大小,所以它应该毫不犹豫地展开循环。

但这并不意味着编译器会一味地展开或不是所有的循环!

在您的例子,原因很简单:在循环中只包含一个指令 - 在任何平台上的几个字节 - 和编译器知道这是可忽略而无论如何都会采取几乎相同大小所需的汇编代码循环(在x86-64上的sub + mov + jne)。

这就是为什么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; 
} 

...下面的汇编代码:

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  

它不会听你的,因为它会(几乎,这取决于体系结构)不会改变二进制文件的大小,但速度更快。但是,如果你增加一点你的循环计数器......

int mul(int k, int j) 
{ 
    for (int i = 0; i < 20; ++i) 
    volatile int k = j; 

    return k; 
} 

...它遵循了提示:

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 

,如果你把你的循环计数器在5您将获得相同的行为,但你在循环中添加一些代码。总结一下,将所有这些优化标志视为编译器的提示,以及从实用开发者的角度来看。它总是一个权衡,当你建立一个软件,你从来没有想要问所有没有循环展开。

作为最后一点,另一个非常相似的例子是-f(no-)inline-functions标志。我每天都在努力编译器来内联(或不)!我的一些函数(使用GCC的inline关键字和__attribute__ ((noinline))),当我检查汇编代码时,我发现这个smartass仍然有时候正在做它想做的事情,当我想内联一个功能肯定太长时间的味道。而且大多数时候,这是正确的事情,我很高兴!

+0

至少编译器*通常会听'__attribute__(((no)inline))'和快速/严格的数学等东西。我无法想象编译器会忽略严格的数学标志。 – Mysticial