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