web-dev-qa-db-ja.com

x86アセンブリでレジスタをゼロに設定する最良の方法は何ですか:xor、movまたはand?

以下のすべての指示は同じことを行います:%eaxをゼロに設定します。どの方法が最適ですか(必要なマシンサイクルは最も少ない)。

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

TL; DR summaryxor same, sameすべてのCPUに最適な選択肢です。他の方法にはそれ以上の利点はありません。また、少なくとも他の方法に比べていくつかの利点があります。 IntelおよびAMDによって公式に推奨されています。 64ビットモードでは、 2ビットregを書き込むと上位32がゼロになる なので、xor r32, r32を引き続き使用します。 xor r64, r64はREXプレフィックスが必要なため、バイトの無駄です。

さらに悪いことに、Silvermontはxor r32,r32を64ビットのオペランドサイズではなく、デプブレークとしてのみ認識します。したがって、r8..r15をゼロに設定しているためにREXプレフィックスが必要な場合でも、xor r10d,r10dではなくxor r10,r10を使用します。

例:

xor   eax, eax       ; RAX = 0
xor   r10d, r10d     ; R10 = 0
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
mov   eax, 0        ; doesn't touch FLAGS, but not faster and takes more bytes

ベクトルレジスタのゼロ化は通常、pxor xmm, xmmを使用して行うのが最適です。これは通常、gccが行うことです(FP命令で使用する前でも)。

xorps xmm, xmmは理にかなっています。 pxorより1バイト短いですが、xorpsはIntel Nehalemで実行ポート5を必要としますが、pxorはどのポート(0/1/5)でも実行できます。 (Nehalemの整数とFPの間の2cバイパス遅延レイテンシは、通常、関連性がありません。これは、通常、アウトオブオーダー実行により、新しい依存関係チェーンの開始時に非表示になるためです)。

SnBファミリのマイクロアーキテクチャでは、xor-zeroingのフレーバーには実行ポートも必要ありません。 AMDおよびNehalem P6/Core2以前のIntelでは、xorpsおよびpxorは(ベクトル整数命令として)同じ方法で処理されます。

128bベクトル命令のAVXバージョンを使用すると、regの上部もゼロになります。したがって、vpxor xmm, xmm, xmmは、YMM(AVX1/AVX2)またはZMM(AVX512)、または将来のベクトル拡張のゼロ化に適しています。ただし、vpxor ymm, ymm, ymmはエンコードに余分なバイトを必要とせず、同じように実行します。 AVX512 ZMMのゼロ化には余分なバイトが必要になるため(EVEXプレフィックス用)、XMMまたはYMMのゼロ化を優先する必要があります。


一部のCPUは、sub same,samexorのようなゼロ化イディオムとして認識しますが、ゼロ化イディオムを認識するすべてのCPUはxorを認識します。 xorを使用するだけで、どのCPUがどのゼロ化イディオムを認識するかを心配する必要はありません。

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

  • mov reg,0よりも小さいコードサイズ。 (すべてのCPU)
  • 後のコードでの部分的な登録ペナルティを回避します。 (Intel P6-familyおよびSnB-family)。
  • 実行ユニットを使用せず、電力を節約し、実行リソースを解放します。 (Intel SnBファミリー)
  • 小さいuop(即時データなし)は、必要に応じて借用する近くの命令のためにuopキャッシュラインにスペースを残します。 (Intel SnBファミリー)。
  • 物理レジスタファイルのエントリを使い果たしません 。 (少なくともIntel SnBファミリ(およびP4)。おそらく、AMDも同様です。IntelP6ファミリマイクロアーキテクチャのようなROBでレジスタ状態を維持する代わりに、同様のPRFデザインを使用しているためです。)

小さいマシンコードサイズ(5ではなく2バイト)が常に利点です。コード密度が高いと、命令キャッシュミスが少なくなり、命令フェッチが向上し、帯域幅をデコードできる可能性があります。


実行ユニットを使用しないの利点は、Intel SnBファミリマイクロアーキテクチャのxorに対してはわずかですが、電力を節約します。 ALU実行ポートが3つしかないSnBまたはIvBで問題になる可能性が高くなります。 Haswell以降には、mov r32, imm32を含む整数ALU命令を処理できる4つの実行ポートがあるため、スケジューラーによる完全な意思決定(実際には行われません)により、HSWはすべて実行が必要な場合でも1クロックあたり4 uopを維持できますポート。

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

Bruce Dawsonのブログ投稿 Michael Petchが(質問に対するコメントで)リンクしていることは、xorが実行ユニットを必要とせずにregister-renameステージで処理されることを指摘している(未融合ドメインでゼロuops)融合ドメインではまだ1つのuopであるという事実を見逃していました。最新のIntel CPUは、クロックごとに4つの融合ドメインuopを発行および廃止できます。これが、クロック制限ごとに4つのゼロが発生する場所です。レジスタ名変更ハードウェアの複雑さの増加は、デザインの幅を4に制限する理由の1つにすぎません(Bruceは FP math and x87/SSE /丸めの問題 、これを強くお勧めします)。


AMD BulldozerファミリCPUの場合mov immediateは、xorと同じEX0/EX1整数実行ポートで実行されます。 mov reg,regはAGU0/1でも実行できますが、これはレジスタのコピーのみで、イミディエイトからの設定ではありません。したがって、AMDでは、xorに対するmovの唯一の利点は、エンコードが短いことです。物理的なレジスタリソースを節約することもできますが、テストは行っていません。


ゼロ化イディオムを認識部分レジスタペナルティを回避完全レジスタ(P6およびSnBファミリ)とは別に部分レジスタの名前を変更するIntel CPUで。

xorレジスタの上部をゼロにするタグを付けるであるため、xor eax, eax/inc al/inc eaxは、IvB以前のCPUが持つ通常の部分レジスタペナルティを回避します。 xorがなくても、IvBは上位8ビット(AH)が変更されてからレジスタ全体が読み取られ、Haswellがそれを削除する場合にのみ、マージuopが必要です。

Agner Fogのマイクロアーチガイド、pg 98(Pentium Mセクション、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ビットがゼロであることを記憶しています。

そのガイドのpg82は、少なくともPIIIやPMのような初期のP6デザインでは、mov reg, 0がゼロ化イディオムとしてnot認識されないことも確認しています。後のCPUでトランジスタを検出するのにトランジスタを費やした場合、私は非常に驚いたでしょう。


xorはフラグを設定します。これは、条件をテストするときに注意する必要があることを意味します。 setccは、残念ながら8ビットの宛先でのみ使用可能であるため、通常、部分的なレジスタペナルティを避けるように注意する必要があります。

X86-64が16/32/64ビットsetcc r/mの削除されたオペコード(AAMなど)の1つを再利用し、述語が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で最適なパフォーマンスが得られます(ストールなし、uopのマージ、または誤った依存関係)。

フラグ設定命令の前にxorしたくない場合は、より複雑です。例えばある条件で分岐してから、同じフラグから別の条件でsetccしたい場合。例えばcmp/jlesete、および予備のレジスタがない場合、またはxorを使用しないコードパスから完全に除外する場合。

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

setcc/movzx r32, r8を使用することは、フラグ設定命令の前にxor-zeroができない場合、おそらくインテルP6およびSnBファミリーの最適な代替手段です。これは、xor-zeroingの後にテストを繰り返すよりも優れているはずです。 (sahf/lahfまたはpushf/popfも考慮しないでください)。 IvBはmovzx r32, r8を排除できます(つまり、xor-zeroingのような実行ユニットやレイテンシなしでレジスタ名を変更して処理します)。 Haswell以降では、通常のmov命令のみが削除されるため、movzxは実行ユニットを使用し、レイテンシがゼロではないため、test/setcc/movzxxor/test/setccよりも悪くなりますが、少なくともtest/mov r,0/_setcc(古いCPUでははるかに優れています)。

AMD/P4/Silvermontでは、最初にゼロ化なしでsetcc/movzxを使用することは、サブレジスターのdepを個別に追跡しないため、悪いです。レジスタの古い値に誤った依存関係があります。 setcc/test/xorがオプションではない場合、ゼロ化/依存関係の解除にmov reg, 0/setccを使用するのがおそらく最良の選択肢です。

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


and即時ゼロは、私が知っているCPUの古い値とは無関係に特別なケースではないので、依存関係チェーンを壊しません。 xorに勝る利点はなく、多くの欠点があります。

マイクロアーキテクチャーのドキュメントについては、 http://agner.org/optimize/ を参照してください。これには、依存関係の破壊として認識されるゼロ化イディオムが含まれます(例:sub same,sameは一部のCPUにありますが、xor same,sameはすべてに認識されます)。 movは、レジスタの古い値の依存関係チェーンを破壊します(ソース値に関係なく、ゼロであるかどうかにかかわらず、それがmovの仕組みです)。 xorは、srcとdestが同じレジスタである特別な場合にのみ依存チェーンを中断します。そのため、movは、特別に認識された依存関係ブレーカーのリストから除外されます。 (また、ゼロ化イディオムとして認識されていないため、他の利点もあります。)

興味深いことに、最も古いP6デザイン(PProからPentium IIIまで)did n'tは、xor- zeroingを依存ブレーカーとして認識します。部分レジスタを回避するためのゼロ化イディオムとしてのみストールするため、場合によってはbothを使用する価値がありました。 (彼のマイクロアーチpdfのAgner Fogの例6.17を参照してください。これは、P2、P3、さらには(早期?)PMにも当てはまります。 リンクされたブログ投稿へのコメント この見落としはありましたが、私はKatmai PIIIでテストし、@ FanaelはPentium Mでテストしましたが、レイテンシがバインドされたimulチェーンの依存関係を壊さないことがわかりました。)


コードのサイズを改善するか、命令を保存する場合は、コードサイズ以外のパフォーマンスの問題を引き起こさない限り、フラグに触れないようにmovでゼロにします。ただし、xorを使用しない唯一の合理的な理由は、フラグの破壊を避けることです。

195
Peter Cordes