GCC 5.1 循环展开

6

以下是代码

#include <stdio.h>

int main(int argc, char **argv)
{
  int k = 0;
  for( k = 0; k < 20; ++k )
  {
    printf( "%d\n", k ) ;
  }
}

使用 GCC 5.1 或更高版本

-x c -std=c99 -O3 -funroll-all-loops --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000

部分循环展开,它将循环展开十次,然后进行条件跳转。

.LC0:
        .string "%d\n"
main:
        pushq   %rbx
        xorl    %ebx, %ebx
.L2:
        movl    %ebx, %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    1(%rbx), %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    2(%rbx), %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    3(%rbx), %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    4(%rbx), %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    5(%rbx), %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    6(%rbx), %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    7(%rbx), %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    8(%rbx), %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        leal    9(%rbx), %esi
        xorl    %eax, %eax
        movl    $.LC0, %edi
        addl    $10, %ebx
        call    printf
        cmpl    $20, %ebx
        jne     .L2
        xorl    %eax, %eax
        popq    %rbx
        ret

但是使用较旧版本的GCC(如4.9.2)可以创建所需的汇编代码

.LC0:
    .string "%d\n"
main:
    subq    $8, %rsp
    xorl    %edx, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $1, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $2, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $3, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $4, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $5, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $6, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $7, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $8, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $9, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $10, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $11, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $12, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $13, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $14, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $15, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $16, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $17, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $18, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    movl    $19, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    call    __printf_chk
    xorl    %eax, %eax
    addq    $8, %rsp
    ret

有没有一种方法可以强制较新版本的GCC生成相同的输出?
使用https://godbolt.org/g/D1AR6i生成汇编代码。
编辑:没有重复的问题,因为在较新版本的GCC中完全展开循环的问题尚未得到解决。传递--param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000对生成的汇编代码没有影响,这是在GCC >= 5.1中发生的。

1
有趣的是,如果您将for条件更改为例如k < 9,则不会执行展开操作... - LPs
@LPs 使用 GCC 4.9.2,展开循环对于少于9次迭代也有效。https://godbolt.org/g/ZPlCP6 - surrz
@surrz 是的,我在谈论GCC 5.1版本。 - LPs
可能是 GCC 5.1 完全展开循环 的重复问题。 - Artur Kink
1
@surrz: 你可以接受我的答案或者至少对它有些反应吗?就好像你完全忽略了它似的 :-) - Pyves
显示剩余2条评论
1个回答

5
您正在使用的标志和参数不能保证循环完全展开。 GCC文档针对您使用的-funroll-all-loops标志说明如下:

打开完全循环剥离(即完全删除具有小常数迭代次数的循环)

如果编译器决定给定代码段的迭代次数不是“小常数”(即数字太高),则它可能仅执行部分剥离或展开,就像它在这里所做的一样。此外,您使用的param选项只是最大值,但不会强制展开小于设置值的循环。换句话说,如果循环的迭代次数超过您设置的最大值,则该循环将无法完全展开;反之则不成立。
进行优化时要考虑许多因素。在此处,您代码的瓶颈是对printf函数的调用,编译器在进行成本计算时可能会考虑到这一点,或者判断展开指令的大小开销太重要。尽管您告诉它展开循环,但它似乎确定最佳解决方案是将初始循环转换为10个展开和一个跳转。
如果您将printf替换为其他内容,则编译器可能会有所不同的优化。例如,请尝试将其替换为以下内容:
volatile int temp = k;

这个新的代码片段中的循环在较新版本的GCC(以及较旧版本)上将被完全展开。请注意,volatile关键字只是一个技巧,用于防止编译器完全优化掉循环。
总之,据我所知,没有办法强制较新版本的GCC产生相同的输出结果。
另外补充一下,从优化级别-O2开始,在没有任何额外的编译器标志的情况下,近期的Clang会完全展开您的循环。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接