[解決済み] x86アセンブリでレジスタをゼロに設定するには、xor、mov、andのどれが一番良い方法ですか?
質問
次の指示はすべて同じことをします。
%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では。
xorps
と
pxor
も同じように扱われます (ベクトル整数命令として)。
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で異なるレジスタを選択できる場合を除く)、コードサイズは悪くなります。
関連
-
[解決済み] callとapplyの違いは何ですか?
-
[解決済み] 整数の平方根が整数であるかどうかを判断する最速の方法
-
[解決済み] 文字列フォーマット:% vs. .format vs. f-stringリテラル
-
[解決済み] Swift Betaのパフォーマンス:配列のソート
-
[解決済み] 1サイクルあたり4FLOPの理論上の最大値を達成するにはどうすればよいですか?
-
[解決済み] SQL Serverで結果をページ分割する最も良い方法は何ですか?
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
-
[解決済み】インターネット接続が遅い場合のシミュレーション【終了しました
-
[解決済み] gccのffast-mathは実際に何をするのですか?
-
[解決済み] 32ビットレジスタに対するx86-64命令は、なぜフル64ビットレジスタの上部をゼロにするのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 実行時間(高速化)の計算方法
-
[解決済み] apacheサーバーがMaxClientsの設定に達したので、MaxClientsの設定を上げることを検討してください。
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] πの値を最も早く求める方法は何ですか?
-
[解決済み】JSFがゲッターを複数回呼び出す理由
-
[解決済み] 32ビットレジスタに対するx86-64命令は、なぜフル64ビットレジスタの上部をゼロにするのですか?
-
[解決済み] memcachedはRedisに比べれば恐竜のようなもの?[クローズド]
-
[解決済み] フィボナッチヒープを実際に効率よく実装した人はいますか?
-
[解決済み] レジスタを自分自身とXORする目的は何ですか?重複
-
[解決済み] 最下位ビットがセットされる位置