8

ループが通過する反復回数を知ることで、コンパイラは最適化を行うことができます。たとえば、次の 2 つのループを考えてみましょう。

不明な反復回数:

static void bitreverse(vbuf_desc * vbuf)
{
    unsigned int idx = 0;
    unsigned char * img = vbuf->usrptr;

    while(idx < vbuf->bytesused) {
        img[idx] = bitrev[img[idx]];
        idx++;
    }

}

既知の反復回数

static void bitreverse(vbuf_desc * vbuf)
{
    unsigned int idx = 0;
    unsigned char * img = vbuf->usrptr;

    while(idx < 1280*400) {
        img[idx] = bitrev[img[idx]];
        idx++;
    }

}

2 番目のバージョンは、2 回アンロールされるため、より高速なコードにコンパイルされます (少なくとも gcc 4.6.3 および -O2 を使用する ARM では)。最適化時に gcc が考慮に入れるループ回数をアサーションする方法はありますか?

4

2 に答える 2

6

hot関数には、ホットスポットに関するヒントをコンパイラに与えるための属性があります: http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html。関数の前に abb を付けるだけです:

static void bitreverse(vbuf_desc * vbuf) __attribute__ ((pure));

hotgccの ' ' に関するドキュメント:

hot関数の hot 属性は、その関数がコンパイル済みプログラムのホット スポットであることをコンパイラに通知するために使用されます。関数はより積極的に最適化され、多くのターゲットでテキスト セクションの特別なサブセクションに配置されるため、すべてのホットな関数が近くに表示され、局所性が向上します。-fprofile-use を介してプロファイル フィードバックが利用可能な場合、ホットな機能が自動的に検出され、この属性は無視されます。

関数の hot 属性は、4.3 より前の GCC バージョンでは実装されていません。

ラベルの hot 属性は、ラベルに続くパスがそれほど注釈されていないパスよりも可能性が高いことをコンパイラに通知するために使用されます。この属性は、__builtin_expect を使用できない場合 (たとえば、computed goto または asm goto など) に使用されます。

ラベルの hot 属性は、4.8 より前の GCC バージョンでは実装されていません。

また、あなたの周りに __builtin_expect を追加しようとすることもできますidx < vbuf->bytesused- ほとんどの場合、式が真であるというヒントになります。

どちらの場合も、ループが最適化されるかどうかはわかりません。

または、プロファイルに基づく最適化を試すこともできます。-fprofile-generate;を使用してプログラムのプロファイル生成バージョンをビルドします。ターゲット上で実行し、プロファイル データを build-host にコピーして、-fprofile-use. これにより、コンパイラに多くの情報が提供されます。

一部のコンパイラ ( GCC#pragma loop count (N)以外) には、" " や " #pragma unroll (M)"などのループプラグマがあります。IBM で展開します。MSVC のベクトル化プラグマ

ARM コンパイラ ( armcc) には、いくつかのループ プラグマもあります: unroll(n) (経由1 ):

ループのアンローリング: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0348b/CJACACFE.htmlおよびhttp://infocenter.arm.com/help/index.jsp? topic=/com.arm.doc.dui0348b/CJAHJDAB.html

および__promise組み込み:

__promise を使用してベクトル化を改善する

__promise(expr) 組み込み関数は、指定された式がゼロ以外であることをコンパイラに約束します。これにより、コンパイラーは、約束した内容に基づいて冗長なコードを最適化することで、ベクトル化を改善できます。Example 3.21 の逆アセンブルされた出力は、__promise がもたらす違いを示しており、スカラー修正ループを削除することにより、逆アセンブリを単純なベクトル化されたループに減らしています。

例 3.21。__promise(expr) を使用してベクトル化コードを改善する

void f(int *x, int n)
{
    int i;
    __promise((n > 0) && ((n&7)==0));
    for (i=0; i<n;i++) x[i]++;
}
于 2013-01-10T12:20:24.010 に答える
0

実際には、次のように __builtin_expect で正確なカウントを指定できます。

while (idx < __builtin_expect(vbuf->bytesused, 1280*400)) {

vbuf->bytesusedこれは、実行時に 1280*400 であると予想されるgcc を伝えます。

悲しいかな、これは現在の gcc バージョンでの最適化には何もしません。ただし、4.8 は試していません。

編集: すべての標準 C コンパイラには、アサートを介してループ回数を正確に指定する方法があることに気付きました。アサート以来

#include <assert.h>
...
assert(loop_count == 4096);
for (i = 0; i < loop_count; i++) ...

条件が真でない場合、 exit() または abort() を呼び出します。値の伝播を行うコンパイラは、 loop_count の正確な値を認識します。このような最適化のヒントを提供するには、これが最もエレガントで標準に準拠した方法であると常に考えていました。今、私はこの情報を実際に使用する C コンパイラが必要です。

これを高速化する場合、バイト単位のアンロールは、より広いルックアップ テーブルを使用するよりも効果的ではない可能性があることに注意してください。16 ビット テーブルは 128K を占めるため、多くの場合、CPU キャッシュに収まります。データが完全にランダムでない場合は、さらに広いテーブル (3 バイト) が有効な場合があります。

2 バイトの例:

unsigned short *bitrev2;
...
for (idx = 0; idx < vbuf->bytesused; idx += 2) {
    *(unsigned short *)(&img[idx]) = bitrev2[*(unsigned short *)(&img[idx]);
}

これは、与えられた情報に関係なく、コンパイラが実行できない最適化です。

于 2013-01-10T12:56:28.393 に答える