1. ホーム
  2. performance

[解決済み] x86アセンブリでレジスタをゼロに設定するには、xor、mov、andのどれが一番良い方法ですか?

2022-04-28 04:14:13

質問

次の指示はすべて同じことをします。 %eax をゼロにします。どの方法が最適か(最も少ないマシンサイクルで済む)?

xorl   %eax, %eax
mov    $0, %eax
andl   $0, %eax

解決方法は?

TL;DRの概要 : xor same, same すべてのCPUに対応するベストチョイス . 他の方法にはない利点があり、他の方法よりも少なくとも何らかの利点があります。 インテルやAMDが公式に推奨しているし、コンパイラもそうしている。 64ビットモードでは、まだ xor r32, r32 なぜなら 32ビットのREGを書くと、上位32ビットがゼロになります。 . xor r64, r64 はREXプレフィックスが必要なため、1バイトの無駄です。

さらに悪いことに、Silvermontは以下のようなものしか認識しません。 xor r32,r32 は、64ビットのオペランドサイズではなく、dep-breakingである。 そのため r8.r15をゼロにするためREXプレフィックスが必要な場合でも xor r10d,r10d ではなく xor r10,r10 .

GP-integerの例です。

xor   eax, eax       ; RAX = 0.  Including AL=0 etc.
xor   r10d, r10d     ; R10 = 0.  Still prefer 32-bit operand-size.

xor   edx, edx       ; RDX = 0
 ; small code-size alternative:    cdq    ; zero RDX if EAX is already zero

; SUB-OPTIMAL
xor   rax,rax       ; waste of a REX prefix, and extra slow on Silvermont
xor   r10,r10       ; bad on Silvermont (not dep breaking), same as r10d on other CPUs because a REX prefix is still needed for r10d or r10.
mov   eax, 0        ; doesn't touch FLAGS, but not faster and takes more bytes
 and   eax, 0        ; false dependency.  (Microbenchmark experiments might want this)
 sub   eax, eax      ; same as xor on most but not all CPUs; bad on Silvermont for example.

xor   cl, cl        ; false dep on some CPUs, not a zeroing idiom.  Use xor ecx,ecx
mov   cl, 0         ; only 2 bytes, and probably better than xor cl,cl *if* you need to leave the rest of ECX/RCX unmodified


ベクターレジスタをゼロにするのは、通常 pxor xmm, xmm . gccは通常これを行います(FP命令で使用する前でも)。

xorps xmm, xmm は意味を持つことがあります。 よりも1バイト短いです。 pxor しかし xorps は Intel Nehalem の実行ポート 5 を必要とし、一方 pxor はどのポート(0/1/5)でも実行できます。 (Nehalemの整数とFPの間の2cバイパス遅延レイテンシは、通常、アウトオブオーダー実行が新しい依存関係のチェーンの開始時にそれを隠すことができるので、通常は関係ありません).

SnB ファミリーのマイクロアーキテクチャでは、xor-zeroing のどちらの種類も実行ポートさえ必要ではありません。 AMD、およびNehalem P6/Core2以前のIntelでは。 xorpspxor も同じように扱われます (ベクトル整数命令として)。

AVX版の128bベクター命令を使用すると、REGの上位もゼロになるため vpxor xmm, xmm, xmm は、YMM(AVX1/AVX2)やZMM(AVX512)、あるいは将来のベクター拡張をゼロ化するための良い選択です。 vpxor ymm, ymm, ymm はエンコードに余分なバイトを必要とせず、Intelでは同じように動作しますが、Zen2以前のAMDでは遅くなります(2uops)。 AVX512 の ZMM ゼロ化では (EVEX プレフィックスのために) 余分なバイトが必要になるので、XMM または YMM ゼロ化を優先すべきです。

XMM/YMM/ZMMの例

    # Good:
 xorps   xmm0, xmm0         ; smallest code size (for non-AVX)
 pxor    xmm0, xmm0         ; costs an extra byte, runs on any port on Nehalem.
 xorps   xmm15, xmm15       ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX.  Code-size is the only penalty.

   # Good with AVX:
 vpxor xmm0, xmm0, xmm0    ; zeros X/Y/ZMM0
 vpxor xmm15, xmm0, xmm0   ; zeros X/Y/ZMM15, still only 2-byte VEX prefix

#sub-optimal AVX
 vpxor xmm15, xmm15, xmm15  ; 3-byte VEX prefix because of high source reg
 vpxor ymm0, ymm0, ymm0     ; decodes to 2 uops on AMD before Zen2


    # Good with AVX512
 vpxor  xmm15,  xmm0, xmm0     ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
 vpxord xmm30, xmm30, xmm30    ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD.  May be worth using only high regs to avoid needing vzeroupper in short functions.
    # Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
 vpxord zmm30, zmm30, zmm30    ; Without AVX512VL you have to use a 512-bit instruction.

# sub-optimal with AVX512 (even without AVX512VL)
 vpxord  zmm0, zmm0, zmm0      ; EVEX prefix (4 bytes), and a 512-bit uop.  Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.

参照 AMD Jaguar/Bulldozer/Zen での vxorps-zeroing は ymm よりも xmm レジスタで高速ですか?

Knights LandingのZMMレジスタを1つまたは数個クリアする最も効率的な方法は何ですか?

セミ・リレーション m256の値をすべてONEビットに設定する最速の方法

CPUレジスタの全ビットを効率よく1にする AVX512もカバー k0..7 マスク・レジスタを使用します。SSE/AVX vpcmpeqd は多くの場合dep-breakingですが(それでも1を書き込むためにuopが必要ですが)、AVX512では vpternlogd をZMMのREGに使うことは、dep-breakingにさえならない。 ループ内では、特にAVX512では、ALUのuopでレジスタを再作成する代わりに、他のレジスタからコピーすることを考慮してください。

ただし、一部の AMD CPU (Bulldozer と Zen) では、ベクター reg に対して mov-elimination がありますが、xor-zeroing のためにゼロを書き込む ALU uop がまだ必要です。


様々なuarches上のxorのようなゼロ化イディオムの何が特別なのか

一部のCPUは sub same,same のようなゼロ化イディオムとして xor しかし ゼロ化イディオムを認識するすべての CPU は xor . ただ xor そのため、どのCPUがどのゼロ化イディオムを認識しているかを気にする必要はありません。

xor (とは異なり、認識されたゼロ化イディオムである)。 mov reg, 0 には、明らかな利点と微妙な利点があります(要約リスト、その後、それらについて説明します)。

  • よりも小さなコードサイズ mov reg,0 . (全CPU)
  • は、後のコードでパーシャルレジスタのペナルティを回避します。 (インテル P6 ファミリおよび SnB ファミリ).
  • 実行ユニットを使用しないため、消費電力を削減し、実行リソースを解放することができます。 (インテルSnBファミリ)
  • より小さなuop(即時データなし)は、uopキャッシュラインに、必要に応じて近くの命令を借りるためのスペースを残しています。 (Intel SnB-family)。
  • 物理レジスタ・ファイルのエントリを使い切らない . (Intel SnB ファミリ (と P4) は少なくとも、AMD も Intel P6 ファミリ・マイクロアーキテクチャのように ROB にレジスタの状態を保持するのではなく、同様の PRF デザインを使用しているので、おそらく同じでしょう)。

マシンコードサイズの縮小 (コード密度が高いほど、命令キャッシュの取りこぼしが少なくなり、命令フェッチやデコードの帯域幅が向上する可能性があるからです。


のメリットは 実行単位を使用しない Intel SnB ファミリーのマイクロアーキテクチャで xor を使用することはマイナーですが、電力を節約することができます。 ALU実行ポートが3つしかないSnBやIvBでは、より重要になる可能性が高いです。 Haswell 以降は 4 つの実行ポートがあり、次のような整数 ALU 命令を扱うことができます。 mov r32, imm32 そのため、スケジューラによる完璧な判断があれば(実際にはそうなるとは限りませんが)、HSWは、すべてのALU実行ポートが必要な場合でも、1クロックあたり4uopsを維持することが可能です。

参照 レジスタのゼロ化に関する別の質問に対する私の答え をご覧ください。

Bruce Dawsonのブログ記事 マイケル・ペッチがリンクした(質問に対するコメントで)指摘されているのは xor は、実行ユニットを必要とせず、レジスタ名の変更段階で処理されます(非融合領域では0uop)。しかし、融合領域ではまだ1uopであることを見逃していました。 最近のIntel CPUは、1クロックあたり4個のフューズドドメインのuopを発行できる。 これが、1クロックあたり4ゼロという制限の由来だ。 レジスタ名変更ハードウェアの複雑化は、設計の幅を4つに制限する理由の1つに過ぎません(Bruceは、以下のシリーズなど、非常に優れたブログ記事を書いています)。 FP計算とx87/SSE/丸め問題 これは非常におすすめです。)


AMD BulldozerファミリーのCPUの場合 , mov immediate と同じ EX0/EX1 整数実行ポートで実行されます。 xor . mov reg,reg はAGU0/1でも実行できますが、これはレジスタのコピーのみで、即値からの設定はできません。 つまり、AMDでは xor を超える mov の方が短いエンコーディングです。 また、物理的なレジスタのリソースも節約できるかもしれませんが、私はテストを見たことがありません。


認識されているゼロ化イディオム パーシャルレジスターのペナルティを回避する 部分レジスタの名前をフルレジスタとは別に変更する Intel CPU (P6 & SnB ファミリ) において。

xor 意志 レジスタの上部がゼロになったというタグを付ける ということで xor eax, eax / inc al / inc eax は、IvB以前のCPUが持つ通常のパーシャルレジスタのペナルティを回避することができます。 また xor IvBでは、上位8ビット( AH が変更され、その後レジスタ全体が読み出されるのですが、Haswellはそれさえも削除しています。

Agner Fogのmicroarch guide, pg 98(PentiumMの項、SnBなど後の項でも参照)より。

<ブロッククオート

プロセッサは、レジスタと自分自身とのXORを設定することとして認識します。 をゼロにする。レジスタの特別なタグが、上位部分を記憶しています。 がゼロであるため、EAX = AL となります。このタグは、次のような場合でも記憶されます。 をループさせる。

    ; Example    7.9. Partial register problem avoided in loop
    xor    eax, eax
    mov    ecx, 100
LL:
    mov    al, [esi]
    mov    [edi], eax    ; No extra uop
    inc    esi
    add    edi, 4
    dec    ecx
    jnz    LL

(pg82より)。プロセッサは、EAXの上位24ビットが0であることを記憶している限りは 割り込み、予測ミス、その他のシリアライズイベントが発生しないようにします。

そのガイドのpg82でも確認されています。 mov reg, 0 ではない 少なくともPIIIやPMのような初期のP6デザインでは、ゼロ化イディオムとして認識されています。 少なくともPIIIやPMのような初期のP6設計では、ゼロイングイディオムとして認識されています。


xor フラグを立てる ということは、条件をテストするときに注意しなければならない。 このため setcc は、残念ながら8bitのデスティネーションでしか利用できません。 部分登録のペナルティを避けるために、通常、注意する必要があります。

x86-64が削除されたオペコードの1つ(AAMなど)を16/32/64ビットに再利用してくれればよかったのですが。 setcc r/m 述語は、r/mフィールドのソースレジスタ3ビットフィールドにエンコードされています(他のいくつかのシングルオペランド命令が、オペコードビットとしてそれらを使用する方法です)。 しかし、彼らはそれをしなかったし、それはいずれにせよx86-32の助けにはならないだろう。

理想的には xor / フラグを設定する / setcc / フル・レジスタを読み出す。

...
call  some_func
xor     ecx,ecx    ; zero *before* the test
test    eax,eax
setnz   cl         ; cl = (some_func() != 0)
add     ebx, ecx   ; no partial-register penalty here

これは、すべてのCPUで最適なパフォーマンスを発揮します(ストール、uopsのマージ、誤った依存関係なし)。

フラグ設定命令の前にxorを使いたくない場合、事態はより複雑になる 例えば、ある条件で分岐し、同じフラグから別の条件でsetccしたい場合など。 cmp/jle , sete を使いたいが、予備のレジスタがない、あるいは xor を完全にノットテイクコードパスの外に出してください。

フラグに影響を与えないゼロ化イディオムは認められていないので、最適な選択はターゲットマイクロアーキテクチャに依存します。 Core2 では、マージ uop を挿入すると 2 サイクルまたは 3 サイクルのストールが発生する可能性があります。 SnBではより安価になるようですが、測定にあまり時間をかけていません。 使用方法 mov reg, 0 / setcc は、古いIntelのCPUでは大きなペナルティがあり、新しいIntelではまだ多少悪くなります。

使用方法 setcc / movzx r32, r8 は、Intel P6 & SnB ファミリーで、フラグ設定命令の前に xor-zeroができない場合、おそらく最良の代替案です。 これはxor-zeroの後にテストを繰り返すよりも良いはずです。 (この場合 sahf / lahf または pushf / popf ). IvBは、以下を排除することができます。 movzx r32, r8 (つまり、xor-zeroingのように実行ユニットやレイテンシがなく、レジスタのリネームで処理する)。 Haswell 以降は、通常の mov 命令では movzx は実行ユニットを必要とし、レイテンシが0ではないため、test/ setcc / movzx より悪い xor /test/ setcc とはいえ、少なくともtest/と同程度の性能はあります。 mov r,0 / setcc (古いCPUではもっと良い)。

使用方法 setcc / movzx AMD/P4/Silvermontでは、サブレジスタのdepを別々にトラッキングしないので、最初にゼロにしないのはまずいです。 なぜなら、AMD/P4/Svermontでは、サブレジスタのdepを個別に追跡しないからです。 使用方法 mov reg, 0 / setcc の場合、ゼロ化/依存性解消のために、おそらく最良の選択肢となるでしょう。 xor /test/ setcc はオプションではありません。

もちろん setcc の出力が8ビットより広ければ、何もゼロにする必要はありません。 しかし、最近長い依存関係の連鎖の一部であったレジスタを選択した場合、P6 / SnB以外のCPUの誤った依存関係に注意する必要があります。 (また、使用しているレジスタの一部を保存/復元するような関数を呼び出すと、部分的なレジスタストールや余分なuopが発生する可能性があるので注意が必要です。)


and 即ゼロで は、私が知る限りどのCPUでも古い値から独立したものとして特別扱いされないので、依存関係の連鎖を断ち切ることができません。 そのため xor と多くのデメリットがあります。

というときに、マイクロベンチマークを書くのにのみ有効です。 欲しい 遅延テストの一部として依存性を持つが、ゼロと加算によって既知の値を作成したい。


参照 http://agner.org/optimize/ マイクロアークの詳細はこちら また、どのゼロ化イディオムが依存関係の打破として認識されるかを含みます(例えば sub same,same は一部のCPUで動作するが、すべてのCPUでは動作しない。 xor same,same はすべてで認識されます)。 mov は、レジスタの古い値に対する依存関係の連鎖を断ち切ります(ソース値がゼロであろうとなかろうと、それは、どのように mov が動作します)。 xor は、src と dest が同じレジスタであるという特殊なケースで依存関係の鎖を断ち切るだけです。 mov のリストから外されています。 特に 依存性解消の認識 (また、ゼロ化イディオムとして認識されないため、他の利点もあります。)

興味深いことに、最も古いP6デザイン(PProからPentium IIIまで)は はなかった。 認識 xor -ゼロイングは依存関係の破壊であり、パーシャルレジスタのストールを回避するためのゼロイングイディオムとしてのみ使用されます。 であるため、場合によっては 両方とも mov で、次に xor -の順にゼロにしてデップを解除し、再度ゼロにする+内部タグのビットを上位ビットがゼロになるように設定して、EAX=AX=ALとする。

Agner Fog氏のmicroarchのpdfの例6.17.を参照してください。 彼は、これはP2、P3、さらに(初期の)PMにも適用されると言っています。 リンク先のブログ記事へのコメント はこの見落としがあったのは PPro だけだと言っていますが、私は Katmai PIII で、@Fanael は Pentium M でテストしましたが、二人とも遅延バウンドの依存関係が壊れないことを確認しました。 imul の連鎖が発生します。 これは残念ながら、Agner Fogの結果を裏付けるものです。


TL:DRです。

その方が本当にコードがきれいになるなら、あるいは命令を節約できるのなら、もちろん mov を使えば、コードサイズ以外のパフォーマンス上の問題を引き起こさない限り、フラグに触れることはありません。 フラグに触れないようにすることだけが xor しかし、予備のレジスタがあれば、フラグを設定する前にxor-zeroを設定できる場合があります。

mov -の前にゼロを置く。 setcc よりもレイテンシーに優れています。 movzx reg32, reg8 の後(Intelで異なるレジスタを選択できる場合を除く)、コードサイズは悪くなります。