如果循环的迭代次数在编译时未知,GCC 如何展开循环?

How can GCC unroll a loop if its number of iterations is unknown at compile time?

当我找到选项 -funroll-all-loops 时,我正在阅读 optimization options for GCC

它的描述是:

Unroll all loops, even if their number of iterations is uncertain when the loop is entered. This usually makes programs run more slowly. '-funroll-all-loops' implies the same options as '-funroll-loops'

如果循环的迭代次数在编译时未知,编译器如何展开循环?编译器不需要这些信息来展开它吗?它生成什么相应的 C 代码,如果它通常使程序 运行 变慢,这在什么情况下会有用?

您不能假设编译器的中间表示存在 对应的 C 代码 这样的东西。但在这种情况下,我希望最接近的等价物类似于 Duff's Device,这是一个可以在计算位置输入的序列(通常在循环中)。

它可以做类似的事情:

while(n >= 8){
  foo(); foo(); foo(); foo(); foo(); foo(); foo(); foo(); 
  n -= 8;
}
while(n > 0){
  foo();
  n--;
}

当然,Duff 的设备可以省去编写第二个循环的麻烦。

为什么要这样做?这取决于用户。 如果 foo() 花费多于几个周期, 如果原始循环花费的总挂钟时间少于 5%, 如果 n 通常很小,可能不值得这么麻烦。

这里有一些 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 的幂展开允许编译器使用掩码而不是(昂贵的)除法运算。

in what contexts could this be useful if it usually makes the programs run more slowly?

好吧,他们假设如果您选择此选项,您知道自己在做什么,否则您不应该使用此选项。

gcc 是做什么的,好吧,我使用了这个示例程序:

#include <stdio.h>

void f(int j )
{
  for( int k = 0; k < j; ++k )
  {
    printf( "%d\n", k ) ;
  }
}

并用 godbolt 测试它,它根据剩余的迭代次数生成跳转 table (see it live):

cmpl    , %ebp
movl    , %ebx
je  .L1
testl   %r12d, %r12d
je  .L27
cmpl    , %r12d
je  .L28
cmpl    , %r12d
je  .L29
cmpl    , %r12d
je  .L30
cmpl    , %r12d
je  .L31
cmpl    , %r12d
je  .L32
cmpl    , %r12d
je  .L33