填充具有特定值的矢量(SSE2)的最快方法.模板友好

Lor*_*one 3 gcc sse copy initialization vectorization

我有这个模板类:

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

v_sse用副本填写该字段的最佳方法是val什么?由于我使用向量,我可以使用gcc SSE2内在函数.

Pet*_*des 5

如果我们可以编写一次代码并将其编译为更宽的向量并进行一些小调整就会很好,即使在自动向量化不起作用的情况下也是如此.

我得到了与@hirschhornsalz相同的结果:当使用大于HW支持的向量大小的向量实例化时,大量低效的代码.例如,在A<8>没有AVX512的情况下进行构建会产生64位的船载movvmovsd指令.它对栈上的本地进行一次广播,然后分别读回所有这些值,并将它们写入调用者的struct-return缓冲区.

对于x86,我们可以让gcc为一个带有doublearg(在xmm0中)的函数发出最佳广播,并根据标准调用约定返回一个向量(在x/y/zmm0中):

  • 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舍入模式.
    IDK,如果GCC将使用该广播寻址方式来折叠广播加载到存储器操作数.

但是,gcc也理解shuffle,并且具有__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;
}
Run Code Online (Sandbox Code Playgroud)

在模板函数中,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);
*/
Run Code Online (Sandbox Code Playgroud)

在编译时,我设法让gcc得到内部编译器错误-O0.矢量+模板似乎需要一些工作.(至少,它回到了Uccntu目前正在发布的gcc 4.9.2.上游可能有所改进.)

我使用的第一个想法,即由于shuffle无法编译而作为后备而留下的是gcc在使用带向量和标量的运算符时隐式广播.因此,例如,将标量添加到全零向量将可以解决问题.

问题是除非你使用,否则实际的添加不会被优化掉-ffast-math. -funsafe-math-optimizations不幸的是,不仅仅需要-fno-signaling-nans.我尝试了替代方案+,不能导致FPU异常,例如^(xor)和|(或),但gcc不会在doubles 上做那些.该,操作不会产生一个向量结果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
Run Code Online (Sandbox Code Playgroud)

现在,测试结果:

// 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); }
Run Code Online (Sandbox Code Playgroud)

编译器输出(剥离汇编程序指令):

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
Run Code Online (Sandbox Code Playgroud)

请注意,如果不对此模板进行模板处理,则shuffle方法应该可以正常工作,而只是在代码中使用一个向量大小.因此,从SSE更改为AVX就像在一个地方更改16到32一样简单.但是,您需要多次编译同一个文件以生成SSE版本和AVX版本,您可以在运行时调度它们.(但是,无论如何,你可能还需要一个没有使用VEX指令编码的128位SSE版本.)