3

ユーザー設定に応じて実行するかどうかに関係なく、オプションのコードを実行する関数を作成したいと思います。この関数は CPU を集中的に使用し、分岐予測子があまり良くないため、ifs が含まれていると遅くなります。

私の考えは、関数のメモリ内にコピーを作成し、コードを実行したくないときに NOP をジャンプに置き換えることです。私の作業例は次のようになります。

int Test()
{
    int x = 2;
    for (int i=0 ; i<10 ; i++)
    {
        x *= 2;

        __asm {NOP}; // to skip it replace this
        __asm {NOP}; // by JMP 2 (after the goto)
            x *= 2; // Op to skip or not

        x *= 2;
    }
    return x;
}

私のテストのメインでは、この関数を新しく割り当てられた実行可能メモリにコピーし、NOP を JMP 2 に置き換えて、次の x *= 2 が実行されないようにします。JMP 2 は、実際には「次の 2 バイトをスキップする」ものです。

問題は、スキップするコードを編集してサイズを変更するたびに、JMP オペランドを変更する必要があることです。

この問題を解決する別の方法は次のとおりです。

__asm {NOP}; // to skip it replace this
__asm {NOP}; // by JMP 2 (after the goto)
goto dont_do_it;
    x *= 2; // Op to skip or not
dont_do_it:
x *= 2;

次に、固定サイズのgotoをスキップするかどうかを選択します。残念ながら、完全最適化モードでは、goto と x*=2 はコンパイル時に到達できないため削除されます。

したがって、そのデッドコードを保持する必要があります。

VStudio 2008 を使用しています。

4

7 に答える 7

6

ブランチをループの外に移動するだけで、ブランチのコストを最大 10 削減できます。

int Test()
{
    int x = 2;
    if (should_skip) {
        for (int i=0 ; i<10 ; i++)
        {
            x *= 2;
            x *= 2;
        }
    } else {
        for (int i=0 ; i<10 ; i++)
        {
            x *= 2;
            x *= 2;
            x *= 2;
        }
    }

    return x;
}

この場合、および他の同様の場合、コンパイラは条件付きコードを最適化しようとするのではなく、2 つの可能性を別々に考慮し、何も最適化しないため、ループ本体を最適化するより良い仕事をするようにコンパイラを刺激する可能性があります。死んだように。

この結果、重複したコードが多すぎて維持できない場合は、参照によって x を取るテンプレートを使用します。

    int x = 2;
    if (should_skip) {
        doLoop<true>(x);
    } else {
        doLoop<false>(x);
    }

そして、コンパイラがそれをインライン化することを確認してください。

明らかに、これによりコードサイズが少し増加しますが、これが問題になる場合があります。ただし、どちらの方法でも、この変更によって測定可能なパフォーマンスの向上が見られない場合は、あなたのパフォーマンスも向上しないと思います。

于 2010-04-01T20:32:04.863 に答える
4

コードの順列の数が妥当な場合は、コードを C++ テンプレートとして定義し、すべてのバリアントを生成できます。

于 2010-04-01T20:27:13.700 に答える
4

使用しているコンパイラとプラットフォームを指定しないと、ほとんどの人があなたを助けることができなくなります。たとえば、一部のプラットフォームでは、コード セクションが書き込み可能にならないため、NOP を JMP に置き換えることはできません。

コンパイラによって提供される最適化を選択して選択し、それを推測しようとしています。一般的に、それは悪い考えです。内部ループ ブロック全体をアセンブリに記述して、コンパイラがデッド コードとして削除するのを防ぐか、そこにいまいましい if ステートメントを配置して、コンパイラにそのことをさせます。

また、分岐予測が十分に悪く、提案していることを実行することで何らかの純利益を得ることができるかどうかも疑わしいです。これは時期尚早の最適化のケースではありませんか? 可能な限り明白な方法でコードを書いたのに、そのパフォーマンスが十分ではないと判断したことがありますか? それが私の提案するスタートです。

于 2010-04-01T20:29:17.303 に答える
1

実際の質問に対する実際の回答は次のとおりです。

volatile int y = 0;

int Test() 
{
    int x = 2; 
    for (int i=0 ; i<10 ; i++) 
    { 
        x *= 2; 

        __asm {NOP}; // to skip it replace this 
        __asm {NOP}; // by JMP 2 (after the goto) 
        goto dont_do_it;
    keep_my_code:
        x *= 2; // Op to skip or not 
    dont_do_it: 
        x *= 2; 
    }
    if (y) goto keep_my_code;
    return x; 
} 
于 2010-04-01T20:42:40.743 に答える
0

これを機能させる場合は、プロファイルを作成して、実際に高速であることを確認します。最近のCPUでは、すでにcpuキャッシュにあるコード、さらに悪いことにcpuパイプラインを変更するよりも遅いことができることはほとんどありません。CPUは基本的に、パイプラインにあるすべての作業を破棄して、再開する必要があります。

于 2010-04-01T22:48:34.787 に答える
0

これはx64ですか?関数ポインターと条件付き移動を使用して、分岐予測子を回避できる場合があります。ユーザー設定に基づいてプロシージャのアドレスをロードします。プロシージャの 1 つは、何もしないダミーである可能性があります。これは、インライン ASM がまったくなくても実行できるはずです。

于 2010-04-01T20:28:11.613 に答える
0

これは洞察を与えるかもしれません:

#pragma optimize for Visual Studio .

とはいえ、この特定の問題については、VS asm 出力を参照点として使用して、ASM に手動でコーディングします。

メタ レベルでは、CPU パイプの最適化を開始する前に、これが最適な設計とアルゴリズムであると確信する必要がありました。

于 2010-04-01T21:15:42.220 に答える