C -GCC 5.1循环展开



给定以下代码

#include <stdio.h>
int main(int argc, char **argv)
{
  int k = 0;
  for( k = 0; k < 20; ++k )
  {
    printf( "%dn", 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 "%dn"
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 "%dn"
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

对生成的组件没有影响

您使用的标志和参数不保证循环将完全展开。GCC文档陈述了有关您正在使用的-funroll-all-loops标志的以下内容:

打开完全的环剥离(即,用a完全拆除环 恒定数量的迭代数)

编译器确定给定代码的迭代次数不是"小常数"(即数量太高),则可能只会像在这里这样做的部分剥离或展开。此外,您正在使用的param选项仅是最大值,但不要强迫小于设定值的循环完整展开。换句话说,如果循环具有比您设置的最大值更多的迭代,那么循环将不会完全展开;但是逆不正确。

在做优化时考虑了许多因素。在这里,您的代码中的瓶颈是对printf功能的调用,在进行成本计算时,编译器可能会考虑到这一点,或者判断展开的指令大小太重要了。但是,当您告诉它进行展开循环时,似乎最好的解决方案是用10个展开和跳跃转换初始循环。

如果用其他内容替换printf,则编译器可以优化不同的。例如,尝试用以下内容替换它:

volatile int temp = k;

使用此新代码段的循环将在GCC的较新版本(以及较旧版本)上完全展开。请注意,挥发性关键字只是一个技巧,因此编译器不会完全优化循环。

总结一下,据我所知,无法强制迫使以后版本的GCC产生相同的输出。


作为旁注,从优化级别-O2开始,没有任何其他编译器标志,最近版本的clang完全展开了您的循环。

最新更新