GCCは固定範囲ベースのforループを、より長く可変長であるかのように最適化します。質問する

GCCは固定範囲ベースのforループを、より長く可変長であるかのように最適化します。質問する

POD 構造体の配列があり、1 つのフィールドの合計を計算しようとしています。以下に簡単な例を示します。

struct Item
{
    int x = 0;
    int y = 0;
};

typedef Item Items[2];

struct ItemArray
{
    Items items;

    int sum_x1() const;
    int sum_x2() const;
};

int ItemArray::sum_x1() const
{
    int total = 0;
    for (unsigned ii = 0; ii < 2; ++ii)
    {
        total += items[ii].x;
    }
    return total;
}

int ItemArray::sum_x2() const
{
    int total = 0;
    for (const Item& item : items)
    {
        total += item.x;
    }
    return total;
}

2 つの合計関数は同じことを行います。Clang はこれらを同じようにコンパイルします。しかし、-O3x86_64 上の GCC 6 ではそうではありません。次のようになりますsum_x1()。見栄えは良いです。

  mov eax, DWORD PTR [rdi+8]
  add eax, DWORD PTR [rdi]
  ret

次に以下を見てくださいsum_x2():

  lea rdx, [rdi+16]
  lea rcx, [rdi+8]
  xor eax, eax
  add eax, DWORD PTR [rdi]
  cmp rdx, rcx
  je .L12
  lea rcx, [rdi+16]
  add eax, DWORD PTR [rdi+8]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+24]
  add eax, DWORD PTR [rdi+16]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+32]
  add eax, DWORD PTR [rdi+24]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+40]
  add eax, DWORD PTR [rdi+32]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+48]
  add eax, DWORD PTR [rdi+40]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+56]
  add eax, DWORD PTR [rdi+48]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+64]
  add eax, DWORD PTR [rdi+56]
  cmp rdx, rcx
  je .L2
  lea rcx, [rdi+72]
  add eax, DWORD PTR [rdi+64]
  cmp rdx, rcx
  je .L2
  add eax, DWORD PTR [rdi+72]
  ret
.L2:
  rep ret
.L12:
  rep ret

ループの長さが 2 に固定されているのに、GCC が最大 10 の可変長の展開されたループを生成するのはなぜですか? これはメンバー関数でのみ発生し、sum_x2フリー関数を作成すると修正されます。

ICC もsum_x2()非常に奇妙な最適化を行いますが、生成されるコードはまったく異なります。GCC とは異なり、メンバー関数であるかフリー関数であるかは関係なくsum_x2()、どちらも悪いです。

私は GCC 6 を使用していますが、GCC のすべてのバージョンでこのコードに問題があるようです。-march=haswellサイズ 2 の配列の最大 15 要素の反復処理が追加され、さらに状況が悪化します。GCC 5 および 7 では、SIMD 命令が追加され、さらに複雑なコードが生成されます。

この問題の正確な原因を特定して、コード内で同様の発生箇所を見つけて修正したいと思います。GCC 6 でこの動作を引き起こす原因を理解することは非常に役立ちます。私のコードには範囲ベースの for ループが多数含まれており、それらを削除することはあまり考えられませんが、GCC が適切なコードを生成できない場合は選択の余地がありません。

それを試してみてください:https://godbolt.org/g/9GK4jy

関連する狂気:https://godbolt.org/g/BGYggD(最適なコードは 3 つの命令です。GCC 6 では 8 つの命令が生成され、GCC 7 では 130 の命令が生成されます)

ベストアンサー1

リチャード・ビーナーが私のバグレポート問題は、バージョン 8 より前の GCC では、クラスまたは構造体のフィールドが通常の変数と同じ最適化 (たとえば、一定のループ カウント) の対象であることを理解できなかったことにあるようです。そのため、コンテナーがメンバー変数の場合、コンパイル時にわかっている場合でも、不明な回数を最適にループするためのあらゆる種類の複雑なコードを生成していました。

私の理解では、このバグはおそらく、実際のコードにかなり影響を及ぼします。たとえば、メンバーの小さな配列が C++11 の範囲ベースの for ループの対象になっている箇所などです。

迅速な解決(GCC 8 を対象)をしてくれた Richard Biener に感謝します。

おすすめ記事