さまざまな C++ コンパイラでの SSE コードの組み込みガイド付き最適化の結果に関するこの興味深い記事を読んだ後、特に投稿が数年前のものであるため、独自のテストを行うことにしました。私は MSVC を使用しましたが、これは投稿の作成者が実行したテストでは (VS 2010 バージョンではありましたが) 非常に貧弱であり、非常に基本的なシナリオに固執することにしました: XMM レジスタにいくつかの値をパックし、加算のような単純な操作を実行. この記事では、_mm_set_ps がスカラー移動とアンパック命令の奇妙なシーケンスに変換されているので、見てみましょう。
int _tmain(int argc, _TCHAR* argv[])
{
__m128 foo = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
__m128 bar = _mm_set_ps(5.0f, 6.0f, 7.0f, 8.0f);
__m128 ret = _mm_add_ps(foo, bar);
// need to do something so vars won't be optimized out in Release
float *f = (float *)(&ret);
for (int i = 0; i < 4; i++)
{
cout << "f[" << i << "] = " << f[i] << endl;
}
}
次に、これをコンパイルしてデバッガー内で実行し、逆アセンブリを確認しました。
デバッグ:
__m128 foo = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
00B814F0 movaps xmm0,xmmword ptr ds:[0B87840h]
00B814F7 movaps xmmword ptr [ebp-190h],xmm0
00B814FE movaps xmm0,xmmword ptr [ebp-190h]
00B81505 movaps xmmword ptr [foo],xmm0
= fmm_128 6.0f、7.0f、8.0f);
00B81509 movaps xmm0,xmmword ptr ds:[0B87850h]
00B81510 movaps xmmword ptr [ebp-170h],xmm0
00B81517 movaps xmm0,xmmword ptr [ebp-170h]
00B8151E movaps xmmword ptr [bar],xmm0
_ __mm );
00B81522 movaps xmm0,xmmword ptr [bar]
00B81526 movaps xmm1,xmmword ptr [foo]
00B8152A addps xmm1,xmm0
00B8152D movaps xmmword ptr [ebp-150h],xmm1
00B81534 movaps xmm0,xmmword ptr [ebp-150h]
00B8153B movaps xmmword ptr [ret],xmm0
完全に混乱しています。xmmword を __m128 に入れるのに 4 MOVAPS が必要なのはなぜですか? まず、データを xmm0 に入れます (どこかに格納されている 4 つの float 値のリテラルだと思いますが、それを見る方法はわかりません)。次に、ebp とオフセットが指す場所に xmm0 をコピーします。そこに xmm0 (?) があり、最後にそれを格納するはずの変数の場所に。なぜそんなに仕事をするのですか?
リリース: 今回は、コンパイラが xmmword をメモリに保存することをまったく回避することを期待していました。1 つを xmm0 に、もう 1 つを xmm1 に置き、ADDPS を実行し、結果をメモリに置き、それで完了します。代わりに私は得ました:
__m128 foo = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
__m128 バー = _mm_set_ps(5.0f, 6.0f, 7.0f, 8.0f);
__m128 ret = _mm_add_ps(foo, bar);
003E1009 movaps xmm0,xmmword ptr ds:[3E2130h]
003E1010 push esi
003E1011 movaps xmmword ptr [esp+10h],xmm0
どうやら、ADDPSは必要ありません。コンパイラは、2 つの xmmwords がコンパイル時の定数であることに気付いたので、それらを追加して、結果をコードにリテラルとして入れただけだと思いますか? 私が知る限り、esi はそこでループ カウンターとして使用されているため、奇妙なプッシュはおそらく後続の for ループに関係しています。それでも、事前に計算されたリテラルをデータ セグメントから xmm0 に入れ、次にローカル変数 (esp+10h) に入れ、リテラルを直接使用しないのはなぜですか?
要約すると、デバッグ バージョンは予想以上に愚かでした (あるいは、何も得られなかったのかもしれません) が、リリース バージョンは予想外に巧妙でした。この動作を説明するコメントは大歓迎です。ありがとう。
編集:答えは非常に啓発的でしたが、コンパイラの出力を改善するためにできることがあるかどうかを知りたいので、質問をこれについての説明を求めることから現在の形式に変更しています。
たとえば、コンパイラがfooとbarをメモリに格納せず (追加後に必要ないため)、単に xmmN レジスタにロードしてそこに保持するようにガイドすることは可能でしょうか? もしかしてレトも?引用された記事の著者は、MSVC は「言われたことを正確に実行している」だけだと述べています。__asm ブロックを明示的に記述せずに、コードを改善する (読み取り: メモリ転送を回避する) 方法はありますか? ありがとう。