当我找到选项时,我正在阅读GCC 的优化-funroll-all-loops
选项。
它的描述如下:
展开所有循环,即使在进入循环时它们的迭代次数不确定。这通常会使程序运行得更慢。“-funroll-all-loops”暗示与“-funroll-loops”相同的选项
如果在编译时它的迭代次数未知,编译器如何展开循环?编译器不需要这些信息来展开它吗?它会生成哪些相应的 C 代码,如果它通常会使程序运行得更慢,这在什么情况下会有用?
当我找到选项时,我正在阅读GCC 的优化-funroll-all-loops
选项。
它的描述如下:
展开所有循环,即使在进入循环时它们的迭代次数不确定。这通常会使程序运行得更慢。“-funroll-all-loops”暗示与“-funroll-loops”相同的选项
如果在编译时它的迭代次数未知,编译器如何展开循环?编译器不需要这些信息来展开它吗?它会生成哪些相应的 C 代码,如果它通常会使程序运行得更慢,这在什么情况下会有用?
下面是一些 C 代码,展示了如何做到这一点:
int iterations = 100;
int unrollValue = 8;
while (iterations%unrollvalue)
{
// insert loop code here
iterations--;
}
while (iterations)
{
// insert unrollValue copies of loop code here
iterations-= unrollValue;
}
编译器将用相对跳转替换第一个循环,但这在 C 中不容易表示。请注意,展开 2 的幂允许编译器使用掩码而不是(昂贵的)除法操作。
如果它通常会使程序运行得更慢,这在什么情况下会有用?
好吧,他们假设如果您选择此选项,您就知道自己在做什么,如果不这样做,则不应使用此选项。
gcc 会做什么,我使用了这个示例程序:
#include <stdio.h>
void f(int j )
{
for( int k = 0; k < j; ++k )
{
printf( "%d\n", k ) ;
}
}
并用godbolt对其进行了测试,它会根据剩余的迭代次数生成一个跳转表(现场查看):
cmpl $1, %ebp
movl $1, %ebx
je .L1
testl %r12d, %r12d
je .L27
cmpl $1, %r12d
je .L28
cmpl $2, %r12d
je .L29
cmpl $3, %r12d
je .L30
cmpl $4, %r12d
je .L31
cmpl $5, %r12d
je .L32
cmpl $6, %r12d
je .L33
它可以执行以下操作:
while(n >= 8){
foo(); foo(); foo(); foo(); foo(); foo(); foo(); foo();
n -= 8;
}
while(n > 0){
foo();
n--;
}
当然,达夫的设备可以省去编写第二个循环的麻烦。
为什么这样做?这取决于用户。如果foo()
花费超过几个周期,或者如果原始循环花费的时间少于整个挂钟时间的 5%,或者如果n
通常很小,则可能不值得麻烦。
您不能假设编译器的中间表示存在相应的 C 代码。但在这种情况下,我希望最接近的等价物类似于Duff 的 Device,它是一个可以在计算位置输入的序列(通常在循环中)。