9

反復回数がわかっているが大きいループを展開するように GCC を説得するにはどうすればよいですか?

でコンパイルしてい-O3ます。

もちろん、問題の実際のコードはもっと複雑ですが、同じ動作をする簡単な例を次に示します。

int const constants[] = { 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144 };

int get_sum_1()
{
    int total = 0;
    for (int i = 0; i < CONSTANT_COUNT; ++i)
    {
        total += constants[i];
    }
    return total;
}

...CONSTANT_COUNTが 8 (またはそれ以下) として定義されている場合、GCC はループをアンロールし、定数を伝搬し、関数全体を単純なreturn <value>;. 一方、CONSTANT_COUNTが 9 (またはそれ以上) の場合、ループは展開されず、GCC はループし、定数を読み取り、実行時にそれらを追加するバイナリを生成します。定数を返すだけに最適化されます。(はい、逆コンパイルされたバイナリを見てきました。)

次のように、ループを手動で展開すると、次のようになります。

int get_sum_2()
{
    int total = 0;
    total += constants[0];
    total += constants[1];
    total += constants[2];
    total += constants[3];
    total += constants[4];
    total += constants[5];
    total += constants[6];
    total += constants[7];
    total += constants[8];
    //total += constants[9];
    return total;
}

またはこれ:

#define ADD_CONSTANT(z, v, c) total += constants[v];

int get_sum_2()
{
    int total = 0;
    BOOST_PP_REPEAT(CONSTANT_COUNT, ADD_CONSTANT, _)
    return total;
}

...その後、関数は定数を返すように最適化されます。そのため、GCC は一度アンロールされると、より大きなループの一定の伝播を処理できるように見えます。ハングアップは、最初に長いループを展開することを GCC に検討させるだけのようです。

ただし、 が実行時の式である場合があり、そのような場合でも同じコードが正しく機能する必要があるため、手動展開もBOOST_PP_REPEAT実行可能なオプションでもありません。(これらの場合、パフォーマンスはそれほど重要ではありません。)CONSTANT_COUNT

私は C (C++ ではない) で作業しているため、テンプレートのメタプログラミングもconstexpr利用することもできません。

、 、 、 、 、 、、、および-funroll-loops-funroll-all-loops大きな値を-fpeel-loops設定しようとしましたが、どれも違いがないようです。max-unrolled-insnsmax-average-unrolled-insnsmax-unroll-timesmax-peeled-insnsmax-peel-timesmax-completely-peeled-insnsmax-completely-peel-times

Linux x86_64でGCC 4.8.2を使用しています。

何か案は?不足しているフラグまたはパラメーターはありますか...?

4

2 に答える 2

3

この回避策が実際の問題に適用できるかどうかはわかりませんが、Parabola GNU/Linux を実行している x86_64 で GCC 4.9.0 20140604 (プレリリース) が までの次のループを展開することがわかりましたCONSTANT_COUNT == 33

int
get_sum()
{
  int total = 0;
  int i, j, k = 0;
  for (j = 0; j < 2; ++j)
    {
      for (i = 0; i < CONSTANT_COUNT / 2; ++i)
        {
          total += constants[k++];
        }
    }
  if (CONSTANT_COUNT % 2)
    total += constants[k];
  return total;
}

-O3フラグを渡しただけです。のアセンブリコードget_sumは本当に

movl $561, %eax
ret

試していませんが、パターンをさらに拡張できる可能性があります。

少なくとも私の人間の目には、コードがはるかに複雑に見えるため、これが機能するのは奇妙に思えます。残念ながら、展開を強制するのはかなり煩わしい方法です。コンパイラ フラグの方がはるかに優れていたはずです。

于 2014-09-17T02:20:48.910 に答える
1

GCC には、ループ展開 (および最適化)に関して、あいまいなパラメーターとプログラム引数が多数あります。-funroll-loops-funroll-all-loops--param name=value、(たとえばnamemax-unroll-times....) などで遊ぶことができます。

gcc多くの問題への引数の順序。おそらく-O3最初に配置し、その後に上記の奇妙なオプションを配置することをお勧めします。

ただし、展開を増やしても常にパフォーマンスが向上するとは限りません。

最後になりましたが、展開基準を変更する独自の GCC プラグインをコーディングできます。

賢く使用__builtin_prefetchするとパフォーマンスが向上する可能性があります。この回答を参照してください(ただし、不注意に使用するとパフォーマンスが低下します)

ベンチマークする必要があります。時期尚早のマイクロ最適化は、あなたの時間を大きく失うことになると私は感じています。

于 2014-09-18T19:37:45.657 に答える