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


119

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

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


回答:


222

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

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

GP整数の例:

xor   eax, eax       ; RAX = 0.  Including AL=0 etc.
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
xor   r10,r10       ; bad on Silvermont (not dep breaking), same as r10d everywhere else 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   al, al        ; false dep on some CPUs, not a zeroing idiom.  Use xor eax,eax
mov   al, 0         ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified

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

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

SnBファミリのマイクロアーキテクチャでは、xor-zeroingのどちらのフレーバーも実行ポートを必要としません。AMD、プリNehalemのP6 / Core2のインテルに、xorps及びpxor(ベクトル整数命令など)と同じ方法で処理されます。

128bベクトル命令のAVXバージョンを使用すると、regの上部もゼロになるのでvpxor xmm, xmm, xmm、YMM(AVX1 / AVX2)またはZMM(AVX512)、または将来のベクトル拡張をゼロにするのに適しています。 vpxor ymm, ymm, ymmただし、エンコードに余分なバイトを必要とせず、Intelでは同じように実行されますが、Zen2より前のAMDでは遅くなります(2 uops)。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 /ブルドーザー/ Zenのvxorps-zeroingは、ymmよりもxmmレジスタの方が高速ですか?を参照してくださいそして
騎士ランディングに単一または少数ZMMレジスタをクリアするための最も効率的な方法は何ですか?

準関連:__m256値をすべてのONEビット
設定し、CPUレジスタのすべてのビットを1に効率的に設定する最速の方法は、AVX512 k0..7マスクレジスタも効率的にカバーします。SSE / AVX vpcmpeqdは多くの点vpternlogdで重大な問題を解決しています(ただし、1を書き込むためにuop がまだ必要です)が、ZMM regのAVX512 は重大な問題を解決していません。ループ内では、ALV uopを使用して、特にAVX512でレジスタを再作成する代わりに、別のレジスタからコピーすることを検討してください。

しかし、ゼロ化は安価です:ループ内のxmmレジスターのxorゼロ化は、ベクトルregのmov-eliminationを備えているが、xorのゼロを書き込むためにALU uopを必要とする一部のAMD CPU(BulldozerおよびZen)を除いて、通常はコピーと同じです。 -ゼロ化。


xorのようなイディオムをゼロ化することについて、何が特別なのか

いくつかのCPUは認識sub same,sameのようなゼロイディオムとしてxor、しかし、任意のゼロイディオムを認識し、すべてのCPUが認識しますxorxorどのCPUがどのゼロ化イディオムを認識するかを気にする必要がないように使用してください。

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

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

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


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

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

Michael xorPetchが(質問へのコメントで)リンクしたBruce Dawsonのブログ投稿は、実行ユニット(融合されていないドメインでのuops)を必要とせずに、register-renameステージで処理されることを指摘していますが、それはまだ1つのuopであるという事実を見落としました融合ドメイン内。最新のIntel CPUは、クロックごとに4つの融合ドメインuopsを発行および廃止できます。これが、クロックごとの4つのゼロ制限の由来です。レジスタの名前を変更するハードウェアの複雑さが増したことは、デザインの幅を4に制限する理由の1つにすぎません(ブルースは、FP演算とx87 / SSE /丸めの問題に関する彼のシリーズなど、非常に優れたブログ投稿をいくつか書いています。強くお勧めします)。


AMD BulldozerファミリCPUではmov immediateと同じEX0 / EX1整数実行ポートで実行されますxormov reg,regAGU0 / 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のmicroarchガイド、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もそれmov reg, 0が確認されています少なくともPIIIやPMなどの初期のP6設計では、ゼロ化の慣用句として認識されていないます。彼らが後のCPUでそれを検出するためにトランジスタを費やしたとしたら、私は非常に驚きます。


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

x / 86-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/jleseteそして、予備のレジスタがないか、またはxorされていないコードパスを完全に除外し。

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

フラグ設定命令の前にxor-zeroを実行できない場合は、setcc/ を使用することmovzx r32, r8がおそらくIntel P6&SnBファミリの最良の代替案です。これは、xorゼロ化の後にテストを繰り返すよりも優れているはずです。(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を個別に追跡しないためです。レジスターの古い値に誤ったdepがあります。ゼロ化に/ を使用するmov reg, 0/ setcc依存関係を破壊することは、xor/ test /setccがオプションではないの選択肢です。

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


and即時ゼロの場合は、私が認識しているCPUの古い値とは無関係であるため、特別なケースではないため、依存関係のチェーンが壊れることはありません。それ以上の利点はありませんxor、多くの欠点があります。

レイテンシテストの一部として依存関係が必要であるが、ゼロ化および追加によって既知の値を作成する必要がある場合に、マイクロベンチマークを記述する場合にのみ役立ちます。


microarchの詳細についてはhttp://agner.org/optimize/参照してください。どのゼロ化イディオムが依存関係の破壊として認識されるsub same,sameか(たとえば、一部のCPUではすべてではないが、すべてのCPUではxor same,same認識される)movは、古い値の依存性チェーンを破壊します。 レジスタの(ソース値に関係なく、ゼロかどうか、それがどのようにmov機能するか)。 xorsrcとdestが同じレジスタである特別な場合にのみ依存チェーンを切断します。そのためmov特別に認識された依存関係ブレーカーのリストから除外されます。(また、ゼロ化のイディオムとして認識されないため、他の利点もあります。)

興味深いことに、最も古いP6デザイン(PPro からPentium III)は、xor -zeroingを依存関係のブレーカーとして認識せず、部分的なレジスターストールを回避するためのゼロ化イディオムとしてのみ認識したため、場合によっては両方 movを使用する価値がありましたxor+ depを解除するためにゼロ化してから再びゼロ化します。+内部タグビットを設定して、上位ビットがゼロになるようにし、EAX = AX = ALにします。

Agner Fogの例6.17を参照してください。彼のmicroarch pdfで。これはP2、P3、さらには(初期の?)PMにも当てはまると言います。 リンクされたブログ投稿へのコメントは、この見落としがあったのはPProだけだったと述べていますが、私はKatmai PIIIでテストし、@ FanaelはPentium Mでテストしました。バインドされたimulチェーン。残念ながら、これはAgner Fogの結果を裏付けるものです。


TL:DR:

コードがより良いものになるか、命令が保存movされる場合は、コードサイズ以外のパフォーマンスの問題を引き起こさない限り、フラグに触れないようにゼロを設定してください。フラグを壊さないようにすることはxor、を使用しない唯一の賢明な理由ですが、予備のレジスタがある場合は、フラグを設定する前にxor-zeroを実行できます。

mov-zero before setccはレイテンシがmovzx reg32, reg8後よりも良い(異なるレジスタを選択できるIntelを除く)が、コードサイズはより悪い。


7
ほとんどの算術命令OP R、Sは、異常なCPUによって、レジスタRの内容がレジスタRをターゲットとする以前の命令によって満たされるのを待つように強制されます。これはデータ依存です。重要な点は、Intel / AMDチップには、XOR R、Rが検出されたときにレジスタRのデータ待機必須依存性を解除する特別なハードウェアがあり、他のレジスタのゼロ化命令では必ずしもそうではないということです。これは、XOR命令を即時実行するようにスケジュールできることを意味し、これがIntel / AMD 使用することを推奨する理由です。
Ira Baxter

3
@IraBaxter:うん、そして混乱を避けるために(SOでこの誤解を見たので)、mov reg, srcOO CPUのdepチェーンも壊れます(srcがimm32 、、[mem]または別のレジスターであるかどうかに関係なく)。この依存関係の破れは、srcとdestが同じレジスタである場合にのみ発生する特別なケースではないため、最適化マニュアルでは言及されていません。それ彼らのdestに依存しない命令に対して常に起こります。(popcnt/lzcnt/tzcntdestに誤ったdepがあるというIntelの実装を除く)
Peter Cordes

2
@Zboson:依存関係のない命令の「レイテンシ」は、パイプラインにバブルがあった場合にのみ問題になります。mov-eliminationには適していますが、ゼロレイテンシの利点は、実行がデータの準備が整うのではなく、デコードされた命令を待機しているブランチの予測ミスやI $ミスなどの後でのみ有効になります。しかし、はい、mov-eliminationはmov無料ではなく、待ち時間はゼロです。通常、「実行ポートを使用しない」という部分は重要ではありません。特に、融合ドメインのスループットはボトルネックになりがちです。ミックス内のロードまたはストアで。
Peter Cordes、2015年

2
Agnerによると、KNLは64ビットレジスタの独立性を認識していません。したがってxor r64, r64、バイトを無駄にするだけではありません。あなたが言うようxor r32, r32に、特にKNLでは最良の選択です。詳細については、このmicrarchマニュアルのセクション15.7「独立の特殊なケース」を参照してください。
Zボソン

3
ああ、古き良きMIPSで、必要なときに「ゼロレジスタ」を使用します。
hayalci 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.