web-dev-qa-db-ja.com

CPUレジスタのすべてのビットを効率的に1に設定します

すべてのビットをクリアするために、排他的論理和またはXOR eax, eaxのように表示されることがよくあります。反対のトリックもありますか?

私が考えることができるのは、追加の命令でゼロを反転することだけです。

18
Pascal de Kloe

固定幅の命令を使用するほとんどのアーキテクチャでは、答えはおそらく、符号拡張または反転された即時の退屈な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で機能します。

    欠点:

    • aLUではなく、ストアおよびロード実行ユニットを使用します。 (AMD Bulldozerファミリーでは、整数実行パイプが2つしかないが、デコード/発行/リタイアのスループットがそれよりも高いというまれなケースで、スループットが向上する可能性があります。ただし、テストせずに試してはいけません。)
    • たとえば、ストア/リロードのレイテンシーは、これがSkylakeで実行された後、raxが最大5サイクルの準備ができていないことを意味します。
    • (Intel):スタックエンジンをrsp変更モードにするため、次に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を実行することは、おそらくロードアプローチよりも厳密に優れていますが、コンパイラーにこれを生成させることは困難です。

最善のアプローチは、おそらく周囲のコードに依存します。

__ m256値をすべて1ビットに設定する最速の方法


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レジスタを探しません。

17
Peter Cordes

ピーターはすでに完璧な答えを提供しています。私はただ言及したいのですが、それは文脈にも依存します。

私はかつて、特定の場合に負になることがわかっている数値のsar r64, 63を実行しました。そうでない場合は、すべてのビットを設定する必要はありません。 sarには、いくつかの興味深いフラグを設定するという利点がありますが、63をデコードするのは本当ですか?それなら、mov r64, -1も実行できます。とにかくそれをさせてくれたのは旗だったと思います。

つまり、結論:コンテキスト。ご存知のように、コンパイラが持っていない追加の知識を処理したいので、通常はアセンブリ言語を詳しく調べます。おそらく、値が不要になったレジスタの一部には1が格納されており(論理的なtrue)、それからnegだけです。プログラムのどこかでloopを実行した後、(管理可能であれば)not rcxが不足しているすべてになるようにレジスタの使用法を調整できます。

2
Kai Burghardt