以下のすべての指示は同じことを行います:%eax
をゼロに設定します。どの方法が最適ですか(必要なマシンサイクルは最も少ない)。
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
TL; DR summary:xor 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,same
をxor
のようなゼロ化イディオムとして認識しますが、ゼロ化イディオムを認識するすべてのCPUはxor
を認識します。 xor
を使用するだけで、どのCPUがどのゼロ化イディオムを認識するかを心配する必要はありません。
xor
(mov reg, 0
とは異なり、認識されているゼロ化イディオムである)には、いくつかの明らかな利点と微妙な利点があります(要約リスト、それらについて詳しく説明します)。
mov reg,0
よりも小さいコードサイズ。 (すべてのCPU)小さいマシンコードサイズ(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/jle
、sete
、および予備のレジスタがない場合、または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
/movzx
はxor
/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
を使用しない唯一の合理的な理由は、フラグの破壊を避けることです。