すべてのビットをクリアするために、排他的論理和またはXOR eax, eax
のように表示されることがよくあります。反対のトリックもありますか?
私が考えることができるのは、追加の命令でゼロを反転することだけです。
固定幅の命令を使用するほとんどのアーキテクチャでは、答えはおそらく、符号拡張または反転された即時の退屈な1つの命令mov
、またはmov lo/highペアになります。例えばARMでは、_mvn r0, #0
_(移動しない)。 x86、ARM、ARM64、およびMIPSのgcc asm出力を参照してください Godboltコンパイラエクスプローラーで 。 zseriesasmまたはマシンコードに関するIDK。
ARMでは、_eor r0,r0,r0
_はmov-immediateよりも大幅に劣ります。これは古い値に依存し、特別な場合の処理はありません。メモリの依存関係-順序付け規則 ARM uarchが必要な場合でも特別な大文字と小文字を区別しないようにします。 メモリの順序が弱い他のほとんどのRISC ISAにも同じことが言えますが、 _memory_order_consume
_(C++ 11の用語)にバリアは必要ありません。
x86 xor-zeroingは、可変長の命令セットがあるため特別です。歴史的に、8086 _xor ax,ax
_は直接高速でしたbecauseそれは小さかったです。このイディオムが広く使用されるようになったため(そしてゼロ化はすべてのものよりもはるかに一般的です)、CPU設計者はそれに特別なサポートを提供し、Intel Sandybridgeファミリーやその他のCPUでは_xor eax,eax
_が_mov eax,0
_よりも高速になりました、直接的および間接的なコードサイズの影響を考慮しなくても。 x86アセンブリでレジスタをゼロに設定するための最良の方法は何ですか:xor、mov、またはand? 私が掘り下げることができたのと同じくらい多くのマイクロアーキテクチャの利点について。
X86に固定幅の命令セットがある場合、_mov reg, 0
_はxor-zeroingと同じくらい特別な扱いを受けたのだろうか?おそらく、low8またはlow16を書き込む前に依存関係を壊すことが重要だからです。
最高のパフォーマンスを得るための標準オプション:
mov eax, -1
_:5バイト、_mov r32, imm32
_エンコーディングを使用。 (残念ながら、符号を拡張する_mov r32, imm8
_はありません)。すべてのCPUで優れたパフォーマンス。 r8-r15(REXプレフィックス)の場合は6バイト。mov rax, -1
_:7バイト、_mov r/m64, sign-extended-imm32
_エンコーディングを使用。 (eax
バージョンのREX.W = 1バージョンではありません。10バイトの_mov r64, imm64
_になります)。すべてのCPUで優れたパフォーマンス。パフォーマンスを犠牲にして通常コードサイズを節約する奇妙なオプション:
xor eax,eax
_/_dec rax
_(または_not rax
_):5バイト(32ビットの場合は4 eax
)。欠点:フロントエンドに2つのuopsがあります。それでも、最近のIntelのスケジューラー/実行ユニット用のunfused-domain uopは1つだけです。ここで、 xor-zeroing はフロントエンドで処理されます。 mov
-即時には常に実行ユニットが必要です。 (ただし、整数ALUスループットが、任意のポートを使用できる命令のボトルネックになることはめったにありません。余分なフロントエンド圧力が問題になります)_xor ecx,ecx
_/_lea eax, [rcx-1]
_2つの定数で合計5バイト(rax
で6バイト):個別のゼロ化されたレジスタを残します。すでにゼロ化されたレジスタが必要な場合、これにマイナス面はほとんどありません。 lea
は、ほとんどのCPUで_mov r,i
_よりも少ないポートで実行できますが、これは新しい依存関係チェーンの開始であるため、CPUは、発行後、任意の予備の実行ポートサイクルで実行できます。
最初の定数を_mov reg, imm32
_で実行し、2番目の定数を_lea r32, [base + disp8]
_で実行すると、同じトリックが2つの近くの定数に対して機能します。 disp8の範囲は-128〜 + 127です。それ以外の場合は、_disp32
_が必要です。
_or eax, -1
_:3バイト(rax
の場合は4)、_or r/m32, sign-extended-imm8
_エンコーディングを使用。欠点:レジスタの古い値への誤った依存。
_Push -1
_/_pop rax
_:3バイト。遅いが小さい。エクスプロイト/コードゴルフにのみ推奨されます。 他のほとんどとは異なり、任意の符号拡張-imm8で機能します。
欠点:
rax
が最大5サイクルの準備ができていないことを意味します。rsp
を直接読み取ると、スタック同期uopが必要になります。 (例:_add rsp, 28
_の場合、または_mov eax, [rsp+8]
_の場合)。ベクトルレジスタを_pcmpeqd xmm0,xmm0
_ですべて1に設定することは、ほとんどのCPUで依存関係を破る(Silvermont/KNLではない)として特別な場合ですが、実際に実行ユニットを書き込むには、まだ実行ユニットが必要です。 _pcmpeqb/w/d/q
_はすべて機能しますが、一部のCPUではq
の速度が遅くなります。
AVX2の場合、ymm
と同等の_vpcmpeqd ymm0, ymm0, ymm0
_も最適です。
AVX2のないAVXの場合、選択はあまり明確ではありません。明確な最善のアプローチは1つではありません。コンパイラは さまざまな戦略 を使用します:gccはvmovdqa
で32バイトの定数をロードすることを好みますが、古いclangは128ビットのvpcmpeqd
とそれに続くクロスレーン_vinsertf128
_上半分を埋めます。新しいclangは、vxorps
を使用してレジスターをゼロにし、次にvcmptrueps
を使用してレジスターを埋めます。これはvpcmpeqd
アプローチと道徳的に同等ですが、以前のバージョンのレジスタへの依存を解消するにはvxorps
が必要であり、vcmptrueps
のレイテンシは3です。妥当なデフォルトの選択を行います。
32ビット値からvbroadcastss
を実行することは、おそらくロードアプローチよりも厳密に優れていますが、コンパイラーにこれを生成させることは困難です。
最善のアプローチは、おそらく周囲のコードに依存します。
AVX512比較は、宛先としてマスクレジスタ(_k0
_など)でのみ使用できるため、コンパイラは現在_vpternlogd zmm0,zmm0,zmm0, 0xff
_512bオールワンイディオムとして。 (0xffは、3入力真理値表のすべての要素を_1
_にします)。これは、KNLまたはSKLの依存関係を破るような特別なケースではありませんが、Skylake-AVX512ではクロックあたり2のスループットがあります。これは、より狭い依存関係を使用して打ち負かします-AVXオールワンを破り、それをブロードキャストまたはシャッフルします。
ループ内でオールワンを再生成する必要がある場合、明らかに最も効率的な方法は、_vmov*
_を使用してオールワンレジスタをコピーすることです。これは、最新のCPUでは実行ユニットを使用しません(ただし、フロントエンドの問題の帯域幅を使用します)。ただし、ベクトルレジスタが不足している場合は、定数または_[v]pcmpeq[b/w/d]
_をロードすることをお勧めします。
AVX512の場合、_VPMOVM2D zmm0, k0
_または_VPBROADCASTD zmm0, eax
_を試す価値があります。それぞれに 1cスループットのみ がありますが、(vpternlogd
とは異なり)zmm0の古い値への依存関係を壊す必要があります。ループの外側で_kxnorw k1,k0,k0
_または_mov eax, -1
_を使用して初期化したマスクまたは整数レジスタが必要です。
AVX512マスクレジスタの場合、_kxnorw k1,k0,k0
_は機能しますが、現在のCPUへの依存関係を壊すことはありません。 Intelの最適化マニュアル 収集命令の前にオールワンを生成するために使用することをお勧めしますが、出力と同じ入力レジスタを使用しないことをお勧めします。これにより、他の方法では独立したギャザーがループ内の前のギャザーに依存することを回避できます。 _k0
_は未使用であることが多いため、通常はそこから読み取ることをお勧めします。
_vpcmpeqd k1, zmm0,zmm0
_は機能すると思いますが、zmm0に依存しないk0 = 1イディオムとしては特別な場合ではないでしょう。 (下位16ビットだけでなく64ビットすべてを設定するには、AVX512BW vpcmpeqb
を使用します)
Skylake-AVX512では、マスクレジスタを操作するk
命令 単一のポートでのみ実行 、 kandw
のような単純な命令でも。 (パイプに512bの操作がある場合、Skylake-AVX512はport1でベクターuopsを実行しないため、実行ユニットのスループットが実際のボトルネックになる可能性があることにも注意してください。)
_kmov k0, imm
_はなく、整数またはメモリから移動するだけです。おそらく、同じ、同じが特別なものとして検出されるk
命令はないので、発行/名前変更段階のハードウェアはk
レジスタを探しません。
ピーターはすでに完璧な答えを提供しています。私はただ言及したいのですが、それは文脈にも依存します。
私はかつて、特定の場合に負になることがわかっている数値のsar r64, 63
を実行しました。そうでない場合は、すべてのビットを設定する必要はありません。 sar
には、いくつかの興味深いフラグを設定するという利点がありますが、63
をデコードするのは本当ですか?それなら、mov r64, -1
も実行できます。とにかくそれをさせてくれたのは旗だったと思います。
つまり、結論:コンテキスト。ご存知のように、コンパイラが持っていない追加の知識を処理したいので、通常はアセンブリ言語を詳しく調べます。おそらく、値が不要になったレジスタの一部には1
が格納されており(論理的なtrue
)、それからneg
だけです。プログラムのどこかでloop
を実行した後、(管理可能であれば)not rcx
が不足しているすべてになるようにレジスタの使用法を調整できます。