3

8つのパックされた32ビット浮動小数点数(__m256)がある場合、8つの要素すべての水平方向の合計を抽出する最も速い方法は何ですか?同様に、水平方向の最大値と最小値を取得するにはどうすればよいですか?言い換えれば、次のC ++関数の最適な実装は何ですか?

float sum(__m256 x);  ///< returns sum of all 8 elements
float max(__m256 x);  ///< returns the maximum of all 8 elements
float min(__m256 x);  ///< returns the minimum of all 8 elements
4

4 に答える 4

7

ここにすばやく書き留めます(したがって、テストされていません):

float sum(__m256 x) {
    __m128 hi = _mm256_extractf128_ps(x, 1);
    __m128 lo = _mm256_extractf128_ps(x, 0);
    lo = _mm_add_ps(hi, lo);
    hi = _mm_movehl_ps(hi, lo);
    lo = _mm_add_ps(hi, lo);
    hi = _mm_shuffle_ps(lo, lo, 1);
    lo = _mm_add_ss(hi, lo);
    return _mm_cvtss_f32(lo);
}

最小/最大の場合は、_mm_add_psおよび_mm_add_ss_mm_max_*またはに置き換え_mm_min_*ます。

これは、いくつかの操作では多くの作業になることに注意してください。AVXは、水平方向の操作を効率的に行うことを実際には意図していません。この作業を複数のベクトルにまとめることができれば、より効率的なソリューションが可能になります。

于 2012-12-17T21:28:52.177 に答える
5

Stephen Canonの答えは、水平方向の最大値/最小値を見つけるのにおそらく理想的ですが、水平方向の合計に対してより良い解決策を見つけることができると思います。

float horizontal_add (__m256 a) {
    __m256 t1 = _mm256_hadd_ps(a,a);
    __m256 t2 = _mm256_hadd_ps(t1,t1);
    __m128 t3 = _mm256_extractf128_ps(t2,1);
    __m128 t4 = _mm_add_ss(_mm256_castps256_ps128(t2),t3);
    return _mm_cvtss_f32(t4);        
}
于 2013-09-04T14:33:37.747 に答える
4

avx命令と非avx命令の混合を回避するコードを作成しようとしましたが、floatを含むavxレジスタの水平方向の合計はavxのみで実行できます。

  • 1x vperm2f128
  • 2倍vshufps
  • 3x vaddps

その結果、すべてのエントリに元のレジスタのすべての要素の合計が含まれるレジスタが作成されます。

// permute
//  4, 5, 6, 7, 0, 1, 2, 3
// add
//  0+4, 1+5, 2+6, 3+7, 4+0, 5+1, 6+2, 7+3
// shuffle
//  1+5, 0+4, 3+7, 2+6, 5+1, 4+0, 7+3, 6+2
// add
//  1+5+0+4, 0+4+1+5, 3+7+2+6, 2+6+3+7, 
//  5+1+4+0, 4+0+5+1, 7+3+6+2, 6+2+7+3
// shuffle
//  3+7+2+6, 2+6+3+7, 1+5+0+4, 0+4+1+5, 
//  7+3+6+2, 6+2+7+3, 5+1+4+0, 4+0+5+1
// add
//  3+7+2+6+1+5+0+4, 2+6+3+7+0+4+1+5, 1+5+0+4+3+7+2+6, 0+4+1+5+2+6+3+7,
//  7+3+6+2+5+1+4+0, 6+2+7+3+4+0+5+1, 5+1+4+0+7+3+6+2, 4+0+5+1+6+2+7+3

static inline __m256 hsums(__m256 const& v)
{
    auto x = _mm256_permute2f128_ps(v, v, 1);
    auto y = _mm256_add_ps(v, x);
    x = _mm256_shuffle_ps(y, y, _MM_SHUFFLE(2, 3, 0, 1));
    x = _mm256_add_ps(x, y);
    y = _mm256_shuffle_ps(x, x, _MM_SHUFFLE(1, 0, 3, 2));
    return _mm256_add_ps(x, y);
}

値の取得は、とを使用して簡単に行え_mm256_castps256_ps128ます_mm_cvtss_f32

static inline float hadd(__m256 const& v)
{
    return _mm_cvtss_f32(_mm256_castps256_ps128(hsums(v)));
}

私は他のソリューションに対していくつかの基本的なベンチマークを実行し__rdtscpましたが、Inteli5-2500kの平均CPUサイクル数の点で優れているものは見つかりませんでした。

私が見つけたAgner命令テーブルを見ると(Sandy-Bridgeプロセッサの場合):

                µops    lat.    1/tp    count

this:

vperm2f128      1       2       1       1
vaddps          1       3       1       3
vshufps         1       1       1       2

sum             6       13      6       6

Z boson:

vhaddps         3       5       2       2
vextractf128    1       2       1       1
addss           1       3       1       1

sum             8       15      6       4

Stephen Canon:

vextractf128    1       2       1       1
addps           1       3       1       2
movhlps         1       1       1       1
shufps          1       1       1       1
addss           1       3       1       1

sum             8       13      6       6

私にとって(値がかなり類似しているため)、明らかに優れているものはありません(命令数、µop数、レイテンシー、またはスループットが最も重要かどうかを予測できないため)。 編集、注:私が以下に存在すると想定した潜在的な問題は真実ではありません。 私は、-ymmレジスタに結果があれば十分である-状態切り替えのペナルティを防ぐhsums必要がないためvzeroupper、何らかの種類のレジスタを導入することなく、異なるレジスタを使用する他のavx計算と同時にインターリーブ/実行できるので便利だと思いました。シーケンスポイント。

于 2016-11-09T19:08:18.677 に答える
-1
union ymm {
    __m256 m256;
    struct {
        __m128 m128lo;
        __m128 m128hi;
    };
};

union ymm result = {1,2,3,4,5,6,7,8};
__m256 a = {9,10,11,12,13,14,15,16};

result.m256 = _mm256_add_ps (result.m256, a);
result.m128lo = _mm_hadd_ps (result.m128lo, result.m128hi);
result.m128lo = _mm_hadd_ps (result.m128lo, result.m128hi);
result.m128lo = _mm_hadd_ps (result.m128lo, result.m128hi);
于 2014-06-03T19:27:30.633 に答える