私はこのテンプレートクラスを持っています:
template<size_t D>
struct A{
double v_sse __attribute__ ((vector_size (8*D)));
A(double val){
//what here?
}
};
v_sse
のコピーでフィールドを埋める最良の方法は何val
ですか? ベクトルを使用するので、gcc SSE2 組み込み関数を使用できます。
私はこのテンプレートクラスを持っています:
template<size_t D>
struct A{
double v_sse __attribute__ ((vector_size (8*D)));
A(double val){
//what here?
}
};
v_sse
のコピーでフィールドを埋める最良の方法は何val
ですか? ベクトルを使用するので、gcc SSE2 組み込み関数を使用できます。
自動ベクトル化がうまくいかない場合でも、一度コードを書いて、少し調整するだけでより広いベクトル用にコンパイルできればいいのですが。
@hirschhornsalz と同じ結果が得られました。HW でサポートされているベクトル サイズよりも大きなベクトルでこれをインスタンス化すると、大規模で非効率的なコードになります。たとえばA<8>
、AVX512 なしで構築すると、64 ビットのボートロードmov
とvmovsd
命令が生成されます。スタック上のローカルに 1 つのブロードキャストを実行し、それらの値をすべて個別に読み取り、呼び出し元の構造体戻りバッファーに書き込みます。
x86 の場合、標準の呼び出し規約に従って、引数 (xmm0 内) を受け取り、ベクトル (x/y/zmm0 内) を返す関数に対して最適なブロードキャストを発行するように gcc を取得できます。double
unpckpd xmm0, xmm0
movddup xmm0, xmm0
vmovddup xmm0, xmm0 / vinsertf128 ymm0, ymm0, xmm0, 1
vbroadcastsd ymm, m64
フォームのみが含まれており、メモリ内のデータの呼び出し時にインライン化された場合に使用されると思われます)vbroadcastsd ymm0, xmm0
vbroadcastsd zmm0, xmm0
. (AVX512 は mem からその場でブロードキャストできることに注意してください。つまりVADDPD zmm1 {k1}{z}, zmm2, zmm3/m512/m64bcst{er}
{k1}{z}
、マスク レジスタを結果へのマージまたはゼロ マスクとして使用できることを意味しますm64bcst
。ブロードキャストされる 64 ビット メモリ アドレスを{er}
意味します。MXCSR 丸めモードは、この 1 つの命令に対してオーバーライドできることを意味します。 . ただし、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 バージョンを使用するには、とにかくそれが必要になる場合があります。)