web-dev-qa-db-ja.com

GCCが分岐予測を常に特定の方法で実行するようにするコンパイラのヒントはありますか?

Intelアーキテクチャの場合、GCCコンパイラーに、コードで常に特定の方法で分岐予測を強制するコードを生成するように指示する方法はありますか? Intelハードウェアはこれもサポートしていますか?他のコンパイラやハードウェアはどうですか?

私はこれをC++コードで使用して、高速に実行したい場合を知っており、他のブランチが最近ブランチを取得した場合でも、他のブランチを取得する必要がある場合はスローダウンを気にしません。

for (;;) {
  if (normal) { // How to tell compiler to always branch predict true value?
    doSomethingNormal();
  } else {
    exceptionalCase();
  }
}

Evdzhan Mustafaの質問のフォローとして、ヒントは、プロセッサが最初に命令を検出したときのヒント、後続のすべての分岐予測が正常に機能していることを指定できますか?

109
WilliamKF

C++ 11でありそうな/ありそうもないマクロを定義する正しい方法は次のとおりです。

_#define LIKELY(condition) __builtin_expect(static_cast<bool>(condition), 1)
#define UNLIKELY(condition) __builtin_expect(static_cast<bool>(condition), 0)
_

これらのマクロがこのように定義された場合:

_#define LIKELY(condition) __builtin_expect(!!(condition), 1)
_

これにより、ifステートメントの意味が変わり、コードが破損する可能性があります。次のコードを検討してください。

_#include <iostream>

struct A
{
    explicit operator bool() const { return true; }
    operator int() const { return 0; }
};

#define LIKELY(condition) __builtin_expect((condition), 1)

int main() {
    A a;
    if(a)
        std::cout << "if(a) is true\n";
    if(LIKELY(a))
        std::cout << "if(LIKELY(a)) is true\n";
    else
        std::cout << "if(LIKELY(a)) is false\n";
}
_

そしてその出力:

_if(a) is true
if(LIKELY(a)) is false
_

ご覧のとおり、boolへのキャストとして_!!_を使用したLIKELYの定義は、ifのセマンティクスを破壊します。

ここでのポイントは、operator int()operator bool()を関連付ける必要があるということではありません。これは良い習慣です。

!!(x)の代わりにstatic_cast<bool>(x)を使用すると、 C++ 11コンテキスト変換 のコンテキストが失われます。

21

GCCは、このような機能を提供する関数__builtin_expect(long exp, long c)をサポートしています。ドキュメントを確認できます here

ここで、expは使用される条件であり、cは期待値です。たとえば、あなたが望む場合

if (__builtin_expect(normal, 1))

厄介な構文のため、これは通常、次のような2つのカスタムマクロを定義することによって使用されます。

#define likely(x)    __builtin_expect (!!(x), 1)
#define unlikely(x)  __builtin_expect (!!(x), 0)

タスクを簡単にするためだけに。

次のことに注意してください:

  1. これは非標準です
  2. コンパイラ/ CPU分岐予測器は、そのようなことを決定するのにあなたよりも熟練している可能性が高いため、これは時期尚早なマイクロ最適化になる可能性があります
80
Jack

いいえ、ありません。 (少なくとも最新のx86プロセッサーでは。)

他の回答で言及されている__builtin_expectは、gccがアセンブリコードを配置する方法に影響します。 それは直接 CPUの分岐予測に影響を与えません。もちろん、並べ替えによって引き起こされる分岐予測に間接的な影響があります。コード。しかし、最新のx86プロセッサには、CPUに「このブランチが使用される/使用されない」と指示する命令はありません。

詳細については、この質問を参照してください: Intel x86 0x2E/0x3Eプレフィックス分岐予測は実際に使用されていますか?

明確にするために、__builtin_expectおよび/または-fprofile-arcsの使用canは、コードレイアウトを通じて分岐予測子にヒントを与えることで、コードのパフォーマンスを向上させます(参照 x86-64アセンブリのパフォーマンス最適化-アライメントと分岐予測 )、および「可能性の低い」コードを「可能性の低い」コードから遠ざけることにより、キャッシュの動作を改善します。

38
Artelius

他の回答がすべて適切に示唆しているように、__builtin_expectは、コンパイラにアセンブリコードの配置方法に関するヒントを提供します。 公式ドキュメント 指摘するように、ほとんどの場合、脳に組み込まれたアセンブラーは、GCCチームによって作成されたアセンブラーほど優れていません。推測するのではなく、実際のプロファイルデータを使用してコードを最適化することが常に最善です。

同様の行に沿って、まだ言及されていませんが、コンパイラに「コールド」パスでコードを生成させるGCC固有の方法があります。これには、noinlineおよびcold属性の使用が含まれます。これらの属性は、本来のとおりの動作をします。これらの属性は関数にのみ適用できますが、C++ 11では、インラインラムダ関数を宣言でき、これら2つの属性はラムダ関数にも適用できます。

これは依然としてマイクロ最適化の一般的なカテゴリに該当するため、標準的なアドバイスが適用されますが、テストでは推測されませんが、__builtin_expect。 x86プロセッサのどの世代でも分岐予測ヒント( reference )を使用することはほとんどないため、とにかく影響を与えることができるのはアセンブリコードの順序だけです。エラー処理コードまたは「エッジケース」コードとは何かを知っているので、このアノテーションを使用して、コンパイラーが分岐を予測せず、サイズを最適化するときに「ホット」コードからリンクしないようにすることができます。

サンプル使用法:

void FooTheBar(void* pFoo)
{
    if (pFoo == nullptr)
    {
        // Oh no! A null pointer is an error, but maybe this is a public-facing
        // function, so we have to be prepared for anything. Yet, we don't want
        // the error-handling code to fill up the instruction cache, so we will
        // force it out-of-line and onto a "cold" path.
        [&]() __attribute__((noinline,cold)) {
            HandleError(...);
        }();
    }

    // Do normal stuff
    ⋮
}

さらに良いことに、GCCは、利用可能な場合(たとえば、-fprofile-use)。

こちらの公式ドキュメントをご覧ください: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

15
Cody Gray

__builtin_expectを使用して、コンパイラーに分岐の予測方法を伝えることができます。これは、コードの生成方法に影響を与える可能性があります。通常のプロセッサは、コードを順次高速に実行します。だから書くなら

if (__builtin_expect (x == 0, 0)) ++count;
if (__builtin_expect (y == 0, 0)) ++count;
if (__builtin_expect (z == 0, 0)) ++count;

コンパイラは次のようなコードを生成します

if (x == 0) goto if1;
back1: if (y == 0) goto if2;
back2: if (z == 0) goto if3;
back3: ;
...
if1: ++count; goto back1;
if2: ++count; goto back2;
if3: ++count; goto back3;

ヒントが正しい場合、実際に実行される分岐なしでコードが実行されます。通常のシーケンスよりも高速に実行され、各ifステートメントは条件付きコードを分岐して3つのブランチを実行します。

新しいx86プロセッサには、実行されると予想されるブランチ、または実行されないと予想されるブランチの命令があります(命令プレフィックスがあります。詳細は不明です)。プロセッサがそれを使用しているかどうかはわかりません。分岐予測はこれをうまく処理するため、あまり有用ではありません。したがって、実際にブランチに影響を与えることができるとは思わない予測

3
gnasher729

OPに関しては、いや、GCCにはプロセッサが常に分岐が行われたかどうかを判断する方法はありません。あなたが持っているのは__builtin_expectで、他の人が言うことをします。さらに、分岐が行われるかどうかをプロセッサに伝えたくないと思いますalways。 Intelアーキテクチャなどの今日のプロセッサは、かなり複雑なパターンを認識し、効果的に適応できます。

ただし、デフォルト分岐の実行が予測されるかどうかの制御を想定したい場合があります。分岐統計に関してコードが「コールド」と呼ばれることがわかっている場合。

具体的な例:例外管理コード。定義により、管理コードは例外的に発生しますが、おそらく最大のパフォーマンスが必要な場合(できるだけ早く注意を払う必要がある重大なエラーがあるかもしれません)、したがって、デフォルトの予測を制御することができます。

別の例:入力を分類し、分類の結果を処理するコードにジャンプできます。多くの分類がある場合、プロセッサは統計を収集するかもしれませんが、同じ分類がすぐに行われず、予測リソースが最近呼び出されたコードに充てられるため、統計を失います。プロセッサに「このコードに予測リソースを割り当てないでください」と言うとき、「これをキャッシュしないでください」と言うことができるプリミティブがあればいいのにと思います。

0
TheCppZoo