2012-05-04 45 views
20

インテルアドバンスト・ベクトル・エクステンションをするためのドット積の256ビット版(AVX)は、倍精度浮動小数点変数のための256ビット版(YMMレジスタ)には内積を提供しています。 「なぜ?」質問は別のフォーラム(here)とスタック オーバーフロー(here)で非常に簡単に扱われました。しかし、私が直面している問題は、この欠落した命令を他のAVX命令と効率的に置き換える方法です。インテルAVX:倍精度浮動小数点変数

256ビットバージョンのドット積は、単精度浮動小数点変数に(reference here)の存在:

__m256d _mm256_dp_pd(__m256d m1, __m256d m2, const int mask); 

__m256 _mm256_dp_ps(__m256 m1, __m256 m2, const int mask); 

アイデアは、この欠落命令のための効率的な等価物を見出すことです具体的には、__m128(4つの浮動小数点数)から__m256d(4倍)に変換するコードは、次の命令を使用します。

__m128 val0 = ...; // Four float values 
    __m128 val1 = ...; // 
    __m128 val2 = ...; // 
    __m128 val3 = ...; // 
    __m128 val4 = ...; // 

    __m128 res = _mm_or_ps(_mm_dp_ps(val1, val0, 0xF1), 
       _mm_or_ps(_mm_dp_ps(val2, val0, 0xF2), 
       _mm_or_ps(_mm_dp_ps(val3, val0, 0xF4), 
          _mm_dp_ps(val4, val0, 0xF8)))); 

このコードの結果はval1val0val2val0val3val0val4val0間の内積の結果を含む4つのフロートの_m128ベクトルです。

これは、このヒントを示唆するものでしょうか?

+0

アイデアをありがとうが、私はアプリケーションで倍精度を維持する必要があります。 –

+0

さらに、変換+浮動小数点積は、二点積よりも時間がかかります。 – hirschhornsalz

答えて

21

hadd(残念ながら、上半分と下半分に2 * 2の浮動小数点数を追加します)、上半分を抽出します(シャッフルは同じか多分速くなるはずです)。下半分に。

結果はdotproductの64ビットになります。

__m256d xy = _mm256_mul_pd(x, y); 
__m256d temp = _mm256_hadd_pd(xy, xy); 
__m128d hi128 = _mm256_extractf128_pd(temp, 1); 
__m128d dotproduct = _mm_add_pd((__m128d)temp, hi128); 

編集:
ノルベルト・P.のアイデアの後、私は一度に4つのドットの製品を行うには、このバージョンを拡張しました。

__m256d xy0 = _mm256_mul_pd(x[0], y[0]); 
__m256d xy1 = _mm256_mul_pd(x[1], y[1]); 
__m256d xy2 = _mm256_mul_pd(x[2], y[2]); 
__m256d xy3 = _mm256_mul_pd(x[3], y[3]); 

// low to high: xy00+xy01 xy10+xy11 xy02+xy03 xy12+xy13 
__m256d temp01 = _mm256_hadd_pd(xy0, xy1); 

// low to high: xy20+xy21 xy30+xy31 xy22+xy23 xy32+xy33 
__m256d temp23 = _mm256_hadd_pd(xy2, xy3); 

// low to high: xy02+xy03 xy12+xy13 xy20+xy21 xy30+xy31 
__m256d swapped = _mm256_permute2f128_pd(temp01, temp23, 0x21); 

// low to high: xy00+xy01 xy10+xy11 xy22+xy23 xy32+xy33 
__m256d blended = _mm256_blend_pd(temp01, temp23, 0b1100); 

__m256d dotproduct = _mm256_add_pd(swapped, blended); 
+0

提案していただきありがとうございます。これはうまくいきます。私はより具体的に私の質問を編集しました。 –

+6

Downvoter、説明には気をつけますか? – hirschhornsalz

+0

ありがとう!最後の行を説明できますか?私はそれをうまく理解するためにshureではない。それは '_mm256_add_pd'ですか? –

12

私はいくつかの作業を保存し、同時に2つの内積を実行するためにdrhirsch's answerを延長する:

__m256d xy = _mm256_mul_pd(x, y); 
__m256d zw = _mm256_mul_pd(z, w); 
__m256d temp = _mm256_hadd_pd(xy, zw); 
__m128d hi128 = _mm256_extractf128_pd(temp, 1); 
__m128d dotproduct = _mm_add_pd((__m128d)temp, hi128); 

その後dot(x,y)が低く、二重であるとdot(z,w)dotproductの高い二重です。

2

1つのドット積では、単純に垂直の乗算と水平の合計です(Fastest way to do horizontal float vector sum on x86参照)。 haddはシャッフル2回+ addです。両方の入力=同じベクトルで使用すると、ほとんど常にスループットが最適以下です。

// both elements = dot(x,y) 
__m128d dot1(__m256d x, __m256d y) { 
    __m256d xy = _mm256_mul_pd(x, y); 

    __m128d xylow = _mm256_castps256_pd128(xy); // (__m128d)cast isn't portable 
    __m128d xyhigh = _mm256_extractf128_pd(xy, 1); 
    __m128d sum1 = _mm_add_pd(xylow, xyhigh); 

    __m128d swapped = _mm_shuffle_pd(sum1, sum1, 0b01); // or unpackhi 
    __m128d dotproduct = _mm_add_pd(sum1, swapped); 
    return dotproduct; 
} 

あなたが唯一の内積を必要とする場合、それはに絞り込むので、これは@ hirschhornsalzのシングルベクトルインテルの1つのシャッフルUOPによって答え、およびAMDジャガー/ブルドーザーファミリー/ Ryzen上の大きな勝利よりも優れています256bのものを一杯にするのではなく、すぐに128b。 AMDは256bの操作を2つの128bのuopに分割します。


それはあなたが2つの異なる入力ベクトルとそれを使用している並列に2つのまたは4内積を行うような場合にhaddを使用して価値があることができます。 2組のベクトルのNorbertのdotは、結果をパックしたい場合に最適に見えます。 AVX2 vpermpdを車線横断シャッフルとして使用しても、もっとうまくいく方法はありません。もちろん

あなたは本当に(8以上double秒)dot 1を大きくしたい場合は、(vaddpsのレイテンシを隠蔽するために複数のアキュムレータで)垂直addを使用し、最後に水平方向の加算を行います。利用可能であれば、fmaを使用することもできます。


haddpd内部で2つの異なる方法をxyzwをシャッフルし、垂直addpdにそれを供給し、そしてそれは我々がとにかく手で行いたいものです。 xyzwを別々にしておけば、ドットプロダクトを得るためには別々のレジスタに2シャッフル+2加算する必要があります。だから最初のステップとしてhaddと一緒にそれらをシャッフルすることによって、シャッフルの合計数を加算し、合計のuopカウントにのみ保存します。

/* Norbert's version, for an Intel CPU: 
    __m256d temp = _mm256_hadd_pd(xy, zw); // 2 shuffle + 1 add 
    __m128d hi128 = _mm256_extractf128_pd(temp, 1); // 1 shuffle (lane crossing, higher latency) 
    __m128d dotproduct = _mm_add_pd((__m128d)temp, hi128); // 1 add 
    // 3 shuffle + 2 add 
*/ 

しかしvextractf128が非常に安くなって、そして256B haddは128B hadd限り2倍の費用がかかる、それは別にダウン128Bにそれぞれ256Bの製品を絞り込み、その後128B HADDと組み合わせることに意味を作ることができAMD、ため。

実際には、Agner Fog's tablesによると、haddpd xmm,xmmは、Ryzenでは4 uopsです。 (そして256b ymmバージョンは8 uopsです)。したがって、実際にRyzenで2x vshufpd + vaddpdを手動で使用する方がよい場合は、そのデータが正しいとします。それはそうではないかもしれません:Piledriverのデータは3 uop haddpd xmm,xmmで、メモリオペランドを持つのはわずか4 uopsです。 haddを3つ(またはymmの6つ)のuopとして実装できないということは私には意味がありません。 1 __m256dにパックされた結果と4 dot秒を行うための


、正確な問題がhirschhornsalzの答えは、IntelのCPUのために非常によさそうだ@私が思うに、尋ねました。私は超慎重にそれを勉強していないが、haddとペアで組み合わせると良いです。 vperm2f128はIntel上では効率的です(ただし、AMDではかなり悪い:Ryzenでは8 uops、3cスループットでは1つ)。

関連する問題