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

ループ展開を部分的に行い、ループを 10 回展開してから条件付きジャンプを実行します。

.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

しかし、4.9.2 などの古いバージョンの GCC を使用すると、目的のアセンブリが作成されます

.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 を使用して生成されたアセンブリには影響しません

4

1 に答える 1

5

使用しているフラグとパラメーターは、ループが完全に展開されることを保証するものではありません。GCCのドキュメントには、-funroll-all-loops使用しているフラグに関して次のように記載されています。

完全なループ ピーリングをオンにします (つまり、少数の一定回数の反復でループを完全に削除します)。

与えられたコードの反復回数が「小さな定数」ではない (つまり、回数が多すぎる) とコンパイラが判断した場合、ここで行ったように、部分的なピーリングまたは展開のみを行う場合があります。さらに、使用しているオプションは最大値paramのみですが、設定値より小さいループの完全な展開を強制しません。つまり、ループの反復回数が設定した最大回数を超えている場合、ループは完全には展開されません。しかし、その逆は正しくありません。

最適化を行う際には、多くの要因が考慮されます。ここで、コードのボトルネックは関数の呼び出しprintfであり、コンパイラはコスト計算を行う際におそらくこれを考慮に入れるか、アンロールの命令サイズのオーバーヘッドが重要すぎると判断します。それにもかかわらず、ループを展開するように指示しているため、最初のループを 10 回の展開とジャンプで変換することが最善の解決策であると判断したようです。

他のものに置き換えるprintfと、コンパイラは別の方法で最適化する場合があります。たとえば、次のように置き換えてみてください。

volatile int temp = k;

この新しいコード スニペットを含むループは、GCC の新しいバージョン (および古いバージョン) で完全に展開されます。volatile キーワードは単なるトリックであるため、コンパイラはループを完全に最適化しないことに注意してください。

要約すると、私の知る限り、GCC の新しいバージョンで同じ出力を強制的に生成する方法はありません。


補足として、最適化レベル-O2以降、追加のコンパイラ フラグなしで、Clang の最近のバージョンはループを完全に展開します。

于 2016-06-25T17:09:28.243 に答える