29

現在、SSE-2 命令を使用した x86-64 アセンブリを使用してstrlen()、 、 などの C99 標準ライブラリ文字列関数の高度に最適化されたバージョンをコーディングしています。memset()

これまでのところ、パフォーマンスの点で優れた結果を得ることができましたが、さらに最適化しようとすると、奇妙な動作が発生することがあります。

たとえば、いくつかの単純な命令を追加または削除したり、ジャンプで使用されるローカル ラベルを単純に再編成したりすると、全体的なパフォーマンスが完全に低下します。そして、コードに関してはまったく理由がありません。

だから私の推測では、コードの配置や、予測を誤った分岐に問題があると思います。

同じアーキテクチャ (x86-64) でも、CPU が異なれば分岐予測のアルゴリズムも異なることがわかっています。

しかし、x86-64 で高パフォーマンスを実現するために開発する場合、コードのアライメントと分岐予測に関する一般的なアドバイスはありますか?

特に位置合わせについては、ジャンプ命令で使用されるすべてのラベルが DWORD で位置合わせされていることを確認する必要がありますか?

_func:
    ; ... Some code ...
    test rax, rax
    jz   .label
    ; ... Some code ...
    ret
    .label:
        ; ... Some code ...
        ret

.label:前のコードでは、次のように の前に align ディレクティブを使用する必要があります。

align 4
.label:

もしそうなら、SSE-2 を使用する場合、DWORD に合わせるだけで十分ですか?

分岐予測については、CPU を助けるために、ジャンプ命令で使用されるラベルを整理する「好ましい」方法はありますか? それとも、今日の CPU は、実行時に分岐の回数を数えることで判断できるほど賢いのでしょうか?

編集

わかりました、ここに具体的な例があります - これstrlen()が SSE-2 の始まりです:

_strlen64_sse2:
    mov         rsi,    rdi
    and         rdi,    -16
    pxor        xmm0,   xmm0
    pcmpeqb     xmm0,   [ rdi ]
    pmovmskb    rdx,    xmm0
    ; ...

1000 文字の文字列で 10'000'000 回実行すると、約 0.48 秒かかります。これは問題ありません。
ただし、NULL 文字列の入力はチェックされません。明らかに、簡単なチェックを追加します。

_strlen64_sse2:
    test       rdi,    rdi
    jz          .null
    ; ...

同じテストで、0.59 秒で実行されます。しかし、このチェックの後にコードを調整すると:

_strlen64_sse2:
    test       rdi,    rdi
    jz          .null
    align      8
    ; ...

元のパフォーマンスが戻ってきました。4 は何も変わらないので、アライメントには 8 を使用しました。
誰かがこれを説明し、いつコードセクションを整列するか、または整列しないかについてアドバイスを与えることができますか?

編集2

もちろん、すべてのブランチ ターゲットを揃えるほど単純ではありません。私がそうすると、上記のような特定の場合を除いて、パフォーマンスは通常悪化します。

4

4 に答える 4

3

「分岐先は 16 バイトに揃える」というルールは絶対的なものではありません。このルールの理由は、16 バイトのアライメントでは、16 バイトの命令を 1 サイクルで読み取り、次のサイクルで別の 16 バイトを読み取ることができるためです。ターゲットがオフセット 16n + 2 にある場合でも、プロセッサは 1 サイクルで 14 バイトの命令 (キャッシュ ラインの残り) を読み取ることができ、多くの場合、これで十分です。ただし、オフセット 16n + 15 でループを開始するのは、一度に 1 命令バイトしか読み取れないため、お勧めできません。より便利なのは、ループ全体を可能な限り最小数のキャッシュ ラインに保持することです。

一部のプロセッサでは、分岐予測は、8 または 4 バイト内のすべての分岐が同じ分岐予測子を使用するという奇妙な動作をします。各条件分岐が独自の分岐予測子を使用するように分岐を移動します。

これらの両方に共通しているのは、いくつかのコードを挿入すると動作が変わり、動作が速くなったり遅くなったりする可能性があるということです。

于 2015-07-26T23:28:02.730 に答える