tl;dr 古いバージョンの MSVC のみ
AVX を使用するコードのセクションを使用_mm256_zeroupper();
または_mm256_zeroall();
前後に (関数の引数に応じて前後に)。/arch:AVX
プロジェクト全体ではなく、AVX を含むソース ファイルに対してのみオプションを使用して、レガシー エンコードされた SSE のみのコード パスのサポートが中断されないようにします。
最新の MSVC (および他の主流のコンパイラである GCC/clang/ICC) では、コンパイラはasm 命令をいつ使用するかを認識しています。vzeroupper
組み込み関数で余分な s を強制vzeroupper
すると、インライン化時にパフォーマンスが低下する可能性があります。2021 年に _mm256_zeroupper を使用する必要がありますか? を参照してください。
原因
Intel の記事「Avoiding AVX-SSE Transition Penalties」 ( PDF )が最も適切な説明だと思います。要約は次のように述べています。
ハードウェアが YMM レジスターの上位 128 ビットを保存および復元する必要があるため、プログラム内で 256 ビットのインテル® AVX 命令と従来のインテル® SSE 命令の間で移行すると、パフォーマンスが低下する可能性があります。
AVX命令またはアセンブリが (Intel から紙):
- 128 ビット組み込み命令
- SSE インライン アセンブリ
- インテル® SSE にコンパイルされた C/C++ 浮動小数点コード
- 上記のいずれかを含む関数またはライブラリの呼び出し
これは、SSE を使用して外部コードとリンクするときにペナルティが発生する可能性があることを意味します。
詳細
AVX 命令によって定義される 3 つのプロセッサ状態があり、そのうちの 1 つは、すべてのYMMレジスタが分割され、下半分がSSE 命令によって使用されることを可能にします。Intel のドキュメント「Intel® AVX State Transitions: Migrating SSE Code to AVX」には、これらの状態の図が示されています。
ステート B (AVX-256 モード) の場合、YMM レジスタのすべてのビットが使用されます。SSE 命令が呼び出されると、ステート C への遷移が発生する必要があり、ここでペナルティが発生します。SSE を開始する前に、すべての YMM レジスターの上半分を内部バッファーに保存する必要があります (それらがたまたまゼロであったとしても)。遷移のコストは、「Sandy Bridge ハードウェアで 50 ~ 80 クロック サイクルのオーダー」です。図 2 に示すように、C -> A からのペナルティもあります。
Mystical の回答で参照されているAgner Fog の最適化ガイド(2014-08-07 に更新されたバージョン)の 130 ページ、セクション 9.12、「 VEX モードと非 VEX モードの間の遷移」で、この速度低下を引き起こしている状態切り替えペナルティの詳細を見つけることもできます。 . 彼のガイドによると、この状態への/からの遷移には「Sandy Bridge で約 70 クロック サイクル」かかります。Intel のドキュメントに記載されているように、これは回避可能な移行ペナルティです。
Skylake には、1 回限りのペナルティではなく、ダーティ アッパーを使用したレガシー SSE の誤った依存関係を引き起こす、別のダーティ アッパー メカニズムがあります。 Skylake で VZEROUPPER を使用しないと、この SSE コードが 6 倍遅くなるのはなぜですか?
解像度
移行ペナルティを回避するには、すべてのレガシー SSE コードを削除するか、すべての SSE 命令を 128 ビット命令の VEX エンコード形式に変換するようコンパイラに指示するか (コンパイラが対応している場合)、事前に YMM レジスタを既知のゼロ状態にすることができます。 AVX と SSE コードの間の移行。基本的に、個別の SSE コード パスを維持するには、 AVX 命令を使用するコードの後で、16 個の YMM レジスタすべての上位 128 ビットをゼロにする (VZEROUPPER
命令を発行する) 必要があります。これらのビットを手動でゼロにすると、状態 A への移行が強制され、ハードウェアによって YMM 値を内部バッファーに格納する必要がないため、コストのかかるペナルティが回避されます。この命令を実行する組み込み関数は_mm256_zeroupper
. この組み込みの説明は非常に有益です。
この組み込み関数は、インテル® アドバンスト・ベクトル・エクステンション (インテル® AVX) 命令と従来のインテル® サプリメンタル SIMD 拡張命令 (インテル® SSE) 命令の間で遷移するときに、YMM レジスターの上位ビットをクリアするのに役立ちます。インテル® アドバンスト・ベクトル・エクステンション (インテル® AVX) 命令と従来のインテル® の間で移行する前に、アプリケーションがこの組み込み関数に対応する命令を介してすべての YMM レジスターの上位ビットをクリア (「0」に設定) する場合、移行ペナルティはありません。VZEROUPPER
補足 SIMD 拡張命令 (インテル® SSE) 命令。
Visual Studio 2010 以降 (おそらくそれより古いバージョン) では、immintrin.h でこの組み込み関数を取得します。
他の方法でビットをゼロにしても、ペナルティが解消されないことに注意してください。VZEROUPPER
またはVZEROALL
命令を使用する必要があります。
インテル® コンパイラーによって実装される自動ソリューションの 1 つは、引数が YMM レジスターまたは // データ型でない場合はインテル® AVX コードを含む各関数の先頭に を挿入し、戻り値がVZEROUPPER
YMMでない場合は関数の最後にレジスタまたは//データ型。__m256
__m256d
__m256i
__m256
__m256d
__m256i
野生で
このVZEROUPPER
ソリューションは、SSE と AVX の両方をサポートするライブラリを生成するために FFTW で使用されます。simd-avx.hを参照してください。
/* Use VZEROUPPER to avoid the penalty of switching from AVX to SSE.
See Intel Optimization Manual (April 2011, version 248966), Section
11.3 */
#define VLEAVE _mm256_zeroupper
次に、AVX 命令の組み込み関数を使用して、すべてVLEAVE();
の関数の最後に呼び出されます。