3

私はこのテンプレートクラスを持っています:

template<size_t D>
struct A{
    double v_sse __attribute__ ((vector_size (8*D)));
    A(double val){
        //what here?
    }
};

v_sseのコピーでフィールドを埋める最良の方法は何valですか? ベクトルを使用するので、gcc SSE2 組み込み関数を使用できます。

4

1 に答える 1

5

自動ベクトル化がうまくいかない場合でも、一度コードを書いて、少し調整するだけでより広いベクトル用にコンパイルできればいいのですが。

@hirschhornsalz と同じ結果が得られました。HW でサポートされているベクトル サイズよりも大きなベクトルでこれをインスタンス化すると、大規模で非効率的なコードになります。たとえばA<8>、AVX512 なしで構築すると、64 ビットのボートロードmovvmovsd命令が生成されます。スタック上のローカルに 1 つのブロードキャストを実行し、それらの値をすべて個別に読み取り、呼び出し元の構造体戻りバッファーに書き込みます。

x86 の場合、標準の呼び出し規約に従って、引数 (xmm0 内) を受け取り、ベクトル (x/y/zmm0 内) を返す関数に対して最適なブロードキャストを発行するように gcc を取得できます。double

  • SSE2:unpckpd xmm0, xmm0
  • SSE3:movddup xmm0, xmm0
  • AVX: vmovddup xmm0, xmm0 / vinsertf128 ymm0, ymm0, xmm0, 1
    (AVX1 にはvbroadcastsd ymm, m64フォームのみが含まれており、メモリ内のデータの呼び出し時にインライン化された場合に使用されると思われます)
  • AVX2:vbroadcastsd ymm0, xmm0
  • AVX512: vbroadcastsd zmm0, xmm0. (AVX512 は mem からその場でブロードキャストできることに注意してください。つまり
    VADDPD zmm1 {k1}{z}, zmm2, zmm3/m512/m64bcst{er}
    {k1}{z}、マスク レジスタを結果へのマージまたはゼロ マスクとして使用できることを意味します
    m64bcst。ブロードキャストされる 64 ビット メモリ アドレスを
    {er}意味します。MXCSR 丸めモードは、この 1 つの命令に対してオーバーライドできることを意味します。 .
    IDK (gcc がこのブロードキャスト アドレッシング モードを使用して、ブロードキャスト ロードをメモリ オペランドにフォールドする場合)。

ただし、gcc は shuffles も理解しており__builtin_shuffle、任意のベクトル サイズに対応しています。すべてゼロのコンパイル時の定数マスクを使用すると、シャッフルはブロードキャストになり、gcc はジョブに最適な命令を使用して実行します。

typedef int64_t v4di __attribute__ ((vector_size (32)));
typedef double  v4df __attribute__ ((vector_size (32)));
v4df vecinit4(double v) {
    v4df v_sse;
    typeof (v_sse) v_low = {v};
    v4di shufmask = {0};
    v_sse = __builtin_shuffle (v_low, shufmask );
    return v_sse;
}

テンプレート関数では、gcc 4.9.2 では、両方のベクトルの幅と要素数が同じであり、マスクが int ベクトルであることを認識するのに問題があるようです。テンプレートをインスタンス化しなくてもエラーになるので、型に問題があるのか​​もしれません。クラスをコピーして特定のベクトルサイズにテンプレート化しないと、すべてが完全に機能します。

template<int D> struct A{
    typedef double  dvec __attribute__ ((vector_size (8*D)));
    typedef int64_t ivec __attribute__ ((vector_size (8*D)));
    dvec v_sse;  // typeof(v_sse) is buggy without this typedef, in a template class
    A(double v) {
#ifdef SHUFFLE_BROADCAST  // broken on gcc 4.9.2
    typeof(v_sse)  v_low = {v};
    //int64_t __attribute__ ((vector_size (8*D))) shufmask = {0};
    ivec shufmask = {0, 0};
    v_sse = __builtin_shuffle (v_low, shufmask);  // no idea why this doesn't compile
#else
    typeof (v_sse) zero = {0, 0};
    v_sse = zero + v;  // doesn't optimize away without -ffast-math
#endif
    }
};

/*  doesn't work:
double vec2val  __attribute__ ((vector_size (16))) = {v, v};
double vec4val  __attribute__ ((vector_size (32))) = {v, v, v, v};
v_sse = __builtin_choose_expr (D == 2, vec2val, vec4val);
*/

でコンパイルすると、どうにか gcc を internal-compile-error にできました-O0。ベクトル + テンプレートには、いくつかの作業が必要なようです。(少なくとも、Ubuntu が現在出荷している gcc 4.9.2 ではそうでした。アップストリームは改善されている可能性があります。)

シャッフルがコンパイルされないため、フォールバックとして残した最初のアイデアは、ベクトルとスカラーで演算子を使用すると、gcc が暗黙的にブロードキャストするというものです。したがって、たとえば、すべてゼロのベクトルにスカラーを追加するとうまくいきます。

問題は、使用しない限り、実際の追加が最適化されないことです-ffast-math-funsafe-math-optimizationsだけでなく、残念ながら必須です-fno-signaling-nans。(xor) や(or)+など、FPU 例外を発生させない代替手段を試しましたが、gcc は s ではそれらを実行しません。演算子はのベクトル結果を生成しません。^|double,scalar , vector

これは、単純な初期化子リストを使用してテンプレートを特殊化することで回避できます。適切なジェネリック コンストラクターが機能しない場合は、定義を省略して、特殊化がない場合にコンパイル エラーが発生するようにすることをお勧めします。

#ifndef NO_BROADCAST_SPECIALIZE
// specialized versions with initializer lists to work efficiently even without -ffast-math
// inline keyword prevents an actual definition from being emitted.
template<> inline A<2>::A (double v) {
    typeof (v_sse) val = {v, v};
    v_sse = val;
}
template<> inline A<4>::A (double v) {
    typeof (v_sse) val = {v, v, v, v};
    v_sse = val;
}
template<> inline A<8>::A (double v) {
    typeof (v_sse) val = {v, v, v, v, v, v, v, v};
    v_sse = val;
}
template<> inline A<16>::A (double v) { // AVX1024 or something may exist someday
    typeof (v_sse) val = {v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v};
    v_sse = val;
}
#endif

次に、結果をテストします。

// vecinit4 (from above) included in the asm output too.
// instantiate the templates
A<2> broadcast2(double val) { return A<2>(val); }
A<4> broadcast4(double val) { return A<4>(val); }
A<8> broadcast8(double val) { return A<8>(val); }

コンパイラ出力 (アセンブラ ディレクティブを削除):

g++ -DNO_BROADCAST_SPECIALIZE  -O3 -Wall -mavx512f -march=native vec-gcc.cc -S -masm=intel -o-

_Z8vecinit4d:
    vbroadcastsd    ymm0, xmm0
    ret
_Z10broadcast2d:
    vmovddup        xmm1, xmm0
    vxorpd  xmm0, xmm0, xmm0
    vaddpd  xmm0, xmm1, xmm0
    ret
_Z10broadcast4d:
    vbroadcastsd    ymm1, xmm0
    vxorpd  xmm0, xmm0, xmm0
    vaddpd  ymm0, ymm1, ymm0
    ret
_Z10broadcast8d:
    vbroadcastsd    zmm0, xmm0
    vpxorq  zmm1, zmm1, zmm1
    vaddpd  zmm0, zmm0, zmm1
    ret


g++ -O3 -Wall -mavx512f -march=native vec-gcc.cc -S -masm=intel -o-
# or   g++ -ffast-math -DNO_BROADCAST_SPECIALIZE blah blah.

_Z8vecinit4d:
    vbroadcastsd    ymm0, xmm0
    ret
_Z10broadcast2d:
    vmovddup        xmm0, xmm0
    ret
_Z10broadcast4d:
    vbroadcastsd    ymm0, xmm0
    ret
_Z10broadcast8d:
    vbroadcastsd    zmm0, xmm0
    ret

これをテンプレート化せず、代わりにコード内で 1 つのベクトル サイズのみを使用する場合、シャッフル メソッドは正常に機能するはずであることに注意してください。したがって、SSE から AVX への変更は、16 から 32 への変更と同じくらい簡単です。ただし、同じファイルを複数回コンパイルして、実行時にディスパッチできる SSE バージョンと AVX バージョンを生成する必要があります。(ただし、VEX 命令エンコーディングを使用しない 128 ビット SSE バージョンを使用するには、とにかくそれが必要になる場合があります。)

于 2015-08-04T07:52:51.503 に答える