14

MSVC 2013 と AVX 1 を使用して、レジスタに 8 つの浮動小数点数があります。

__m256 foo = mm256_fmadd_ps(a,b,c);

inline void print(float) {...}ここで、8 つのフロートすべてを呼び出したいと思います。インテル® AVX 組み込み関数により、これがかなり複雑になるようです。

print(_castu32_f32(_mm256_extract_epi32(foo, 0)));
print(_castu32_f32(_mm256_extract_epi32(foo, 1)));
print(_castu32_f32(_mm256_extract_epi32(foo, 2)));
// ...

しかし、MSVC にはこれら 2 つの組み込み関数のいずれもありません。確かに、値をメモリに書き戻してそこからロードすることはできますが、アセンブリ レベルではレジスタをスピルする必要はないと思います。

おまけQ:もちろん書きたい

for(int i = 0; i !=8; ++i) 
    print(_castu32_f32(_mm256_extract_epi32(foo, i)))

しかし、MSVC は、多くの組み込み関数がループ展開を必要とすることを理解していません。で 8x32 浮動小数点数のループを作成するにはどうすればよい__m256 fooですか?

4

4 に答える 4

9

AVX しかない (つまり、AVX2 がない) と仮定すると、次のようなことができます。

float extract_float(const __m128 v, const int i)
{
    float x;
    _MM_EXTRACT_FLOAT(x, v, i);
    return x;
}

void print(const __m128 v)
{
    print(extract_float(v, 0));
    print(extract_float(v, 1));
    print(extract_float(v, 2));
    print(extract_float(v, 3));
}

void print(const __m256 v)
{
    print(_mm256_extractf128_ps(v, 0));
    print(_mm256_extractf128_ps(v, 1));
}

ただし、おそらくユニオンを使用するだけだと思います。

union U256f {
    __m256 v;
    float a[8];
};

void print(const __m256 v)
{
    const U256f u = { v };

    for (int i = 0; i < 8; ++i)
        print(u.a[i]);
}
于 2016-06-03T11:28:59.690 に答える
1

(未完成の回答。誰かに役立つ場合、または私がそれに戻った場合に備えて、とにかく投稿します。 一般に、ベクトル化できないスカラーとインターフェースする必要がある場合は、ベクトルをローカル配列に格納するだけでも悪くありません。その後、一度に 1 つの要素を再読み込みします。)

asmの詳細については、私の他の回答を参照してください。この回答は、C++ の側面に関するものです。


void foo(__m256 v) {
    alignas(32) float vecbuf[8];   // 32-byte aligned array allows aligned store
                                   // avoiding the risk of cache-line splits
    _mm256_store_ps(vecbuf, v);

    float v0 = _mm_cvtss_f32(_mm256_castps256_ps128(v));  // the bottom of the register
    float v1 = vecbuf[1];
    float v2 = vecbuf[2];
    ...
   // or loop over vecbuf[i]
   // if you do need all 8 elements one at a time, this is a good way
}

またはループオーバーvecbuf[i]。ベクター ストアは、その要素の 1 つのスカラー リロードに転送できるため、約 6 サイクルのレイテンシしか発生せず、一度に複数のリロードを実行できます。(したがって、2/クロックのロード スループットを備えた最新の CPU のスループットには非常に適しています。)

低要素のリロードを回避したことに注意してください。レジスタ内のベクトルの下位要素はすでにスカラーfloatです。 _mm_cvtss_f32( _mm256_castps256_ps128(v) )コンパイラの型システムを満足させる方法です。ゼロの asm 命令にコンパイルされるため、文字通り無料です (最適化の失敗のバグを除けば)。(インテルの組み込みガイドを参照してください)。XMM レジスターは、対応する YMM レジスターの下位 128 ビットであり、スカラー float / double は、XMM レジスターの下位 32 ビットまたは 64 ビットです。(上半分のゴミは問題ありません。)

最初のキャストを 1 回行うと、OoO exec は、残りが到着するのを待っている間に何かを行うことができます。シャッフルして 128 と低い 128 で 2 番目の要素を取得することを検討することvunpckhpsvmovhlpsできます。これにより、レイテンシ バブルを埋めるのに役立つ場合は、2 つの要素をすぐに準備できます。

GNU C/C++ では、配列のようなベクトル型にインデックスを付けv[1]たり、 のような変数インデックスを付けたりすることができますv[i]。コンパイラは、シャッフルまたはストア/リロードのいずれかを選択します。

__m256しかし、これは、いくつかの名前付きメンバーとの共用体に関して定義する MSVC には移植できません。

配列への格納と再読み込みは移植可能であり、コンパイラはそれをシャッフルに最適化することさえできます。 (それが望ましくない場合は、生成された asm を確認してください。)

たとえば、clangvecbuf[1]は単純な vshufps に戻るだけの関数を最適化します。 https://godbolt.org/z/tHJH_V


実際にベクトルのすべての要素を合計してスカラー合計にしたい場合は、シャッフルと SIMD add . x86 で水平フロート ベクトル合計を実行する最速の方法

(単一のベクトルの要素に対する乗算、最小、最大、またはその他の連想縮小についても同じです。もちろん、複数のベクトルがある場合は、のように 1 つのベクトルに垂直操作を行います_mm256_add_ps(v1,v2))


Agner Fog の Vector Class Libraryを使用すると、彼のラッパー クラスがオーバーロードoperator[]され、定数ではない引数に対しても期待どおりに機能します。多くの場合、これはストア/リロードにコンパイルされますが、C++ でのコードの記述が容易になります。最適化を有効にすると、おそらくまともな結果が得られます。(ただし、低い要素は、その場で使用されるのではなく、格納/再ロードされる可能性があります。そのため、または何かに特殊なケースvec[0]が必要になる場合があります。)_mm_cvtss_f32(vec)

(VCL は GPL の下でライセンスされていましたが、現在のバージョンは単純な Apache ライセンスです。)

一部の機能のより良いコードを生成するために、Agner の VCL に対するほとんどテストされていない変更を含む私のgithub リポジトリも参照してください。


_MM_EXTRACT_FLOATラッパー マクロがありますが、これは奇妙で、SSE4.1 でしか定義されていません。extractpsSSE4.1 (浮動小数点のバイナリ表現を整数レジスタに抽出したり、メモリに保存したりできる)を使用することを意図していると思います。ただし、宛先が の場合、gcc はそれを FP シャッフルにコンパイルしfloatます。結果をとextractpsして欲しい場合、他のコンパイラがそれを実際の命令にコンパイルしないように注意してください。(これ.floatextractpsinsertpsshufps

3 つの args: が必要なため、奇妙です。そのため、ローカル_MM_EXTRACT_FLOAT(dest, src_m128, idx)の初期化子としても使用できません。float


ベクトルをループするには

gcc はそのようなループを展開しますが、それ以上の場合のみ-O1です。で-O0、エラー メッセージが表示されます。

float bad_hsum(__m128 & fv) {
    float sum = 0;
    for (int i=0 ; i<4 ; i++) {
        float f;
        _MM_EXTRACT_FLOAT(f, fv, i);  // works only with -O1 or higher
        sum += f;
    }
    return sum;
}
于 2016-06-05T03:25:45.883 に答える