gpt4 book ai didi

c++ - GCC 中的循环展开行为

转载 作者:可可西里 更新时间:2023-11-01 16:37:20 33 4
gpt4 key购买 nike

这个问题部分是 GCC 5.1 Loop unrolling 的后续问题.

根据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 仍然意外地执行循环展开上面描述的例子。

这一观察使我想到了以下密切相关的问题。为什么即使禁用了与此行为对应的标志,GCC 仍会执行循环展开?展开是否也由其他标志控制,即使 -funroll-loops 被禁用,这些标志也会使编译器在某些情况下展开循环?有没有办法在 GCC 中完全禁用循环展开(使用 -O0 编译的一部分)?

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

提前致谢,如有任何关于此事的其他见解,我们将不胜感激!

最佳答案

Why does GCC perform loop unrolling even though the flags corresponding to this behaviour are disabled?

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

但这并不意味着编译器将盲目展开或不展开您的所有循环!

在你的例子中,原因很简单:循环只包含一个指令——在任何平台上都只有几个字节——编译器知道这是可以忽略不计的,并且无论如何都会采用与循环所需的汇编代码(sub + mov + jne on 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;
}

... 到以下汇编代码:

 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 但在循环中添加一些代码,您将获得相同的行为。

总而言之,从务实的开发人员的角度来看,将所有这些优化标志视为对编译器的提示。这始终是一种权衡,当您构建软件时,您永远不想要求所有没有循环展开。 p>

作为最后的说明,另一个非常相似的示例是 -f(no-)inline-functions 标志。我每天都在努力让编译器内联(或不内联!)我的一些函数(使用 inline 关键字和 __attribute__ ((noinline)) 与 GCC),以及何时我检查了汇编代码,我发现这个聪明人有时仍在做它想做的事,当我想内联一个对它来说太长的函数时。大多数时候,这是正确的做法,我很高兴!

关于c++ - GCC 中的循环展开行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39478171/

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com