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 はこれらを同じようにコンパイルします。しかし、-O3
x86_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 に感謝します。