我有这个模板类:
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 相同的结果:当使用大于硬件支持的向量大小的向量实例化它时,代码量很大,效率低下。例如A<8>
,在没有 AVX512 的情况下构建会产生大量 64 位mov
和vmovsd
指令。它对堆栈上的本地进行一次广播,然后分别读回所有这些值,并将它们写入调用者的 struct-return 缓冲区。
对于 x86,我们可以让 gccdouble
根据标准调用约定为接受 arg(在 xmm0 中)并返回向量(在 x/y/zmm0 中)的函数发出最佳广播:
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 舍入模式. 但是,gcc 也理解 shuffles,并且具有__builtin_shuffle
任意向量大小。使用全零的编译时常量掩码,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);
*/
使用-O0
. 向量+模板似乎需要一些工作。(至少,它确实回到了 Ubuntu 目前正在发布的 gcc 4.9.2 中。上游可能已经改进了。)
我的第一个想法是,当您使用带有向量和标量的运算符时,gcc 会隐式广播,因为 shuffle 无法编译,因此我将其作为备用。因此,例如,将标量添加到全零向量就可以了。
问题是实际的 add 不会被优化掉,除非你使用-ffast-math
. -funsafe-math-optimizations
不幸的是,不仅仅是-fno-signaling-nans
. 我尝试了+
不会导致 FPU 异常的替代方法,例如^
(xor) 和|
(or),但 gcc 不会在double
s 上执行这些操作。,
运算符不会为 生成向量结果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
请注意,如果您不对其进行模板化,则 shuffle 方法应该可以正常工作,而是在代码中仅使用一种向量大小。因此,从 SSE 更改为 AVX 就像在一个地方将 16 更改为 32 一样简单。但是,您需要多次编译同一个文件以生成一个 SSE 版本和一个 AVX 版本,您可以在运行时将其分派到。(不过,您可能需要它来获得不使用 VEX 指令编码的 128 位 SSE 版本。)