Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly 进行水平SSE矢量求和(或其他缩减)的最快方法_Assembly_Optimization_Floating Point_Sse_Simd_X86 - Fatal编程技术网

Assembly 进行水平SSE矢量求和(或其他缩减)的最快方法

Assembly 进行水平SSE矢量求和(或其他缩减)的最快方法,assembly,optimization,floating-point,sse,simd,x86,Assembly,Optimization,Floating Point,Sse,Simd,X86,给定三(或四)个浮点数的向量。什么是求和的最快方法 SSE(movaps、shuffle、add、movd)是否总是比x87快?SSE3中的水平添加指令值得吗 迁移到FPU,然后是faddp,faddp的成本是多少?最快的特定指令序列是什么 “尽量安排事情,以便一次可以对四个向量求和”将不会被接受为答案。:-)e、 g.对于一个数组求和,您可以使用多个向量累加器进行垂直求和(以隐藏addps延迟),并在循环后减少到一个,但随后您需要对最后一个向量进行水平求和。您可以在SSE3中的两个HADDP指

给定三(或四)个浮点数的向量。什么是求和的最快方法

SSE(movaps、shuffle、add、movd)是否总是比x87快?SSE3中的水平添加指令值得吗

迁移到FPU,然后是faddp,faddp的成本是多少?最快的特定指令序列是什么


“尽量安排事情,以便一次可以对四个向量求和”将不会被接受为答案。:-)e、 g.对于一个数组求和,您可以使用多个向量累加器进行垂直求和(以隐藏addps延迟),并在循环后减少到一个,但随后您需要对最后一个向量进行水平求和。

您可以在SSE3中的两个
HADDP
指令中执行此操作:

v = _mm_hadd_ps(v, v);
v = _mm_hadd_ps(v, v);

这会将总和放入所有元素。

您可以在SSE3中的两个
HADDP
指令中进行此操作:

v = _mm_hadd_ps(v, v);
v = _mm_hadd_ps(v, v);

这将把总和放在所有元素中。

我肯定会尝试SSE 4.2。如果您多次这样做(如果性能有问题,我假设您会这样做),您可以使用(1,1,1,1)预加载一个寄存器,然后对其执行多个dot4(my_vec,one_vec)。是的,它做了一个多余的乘法运算,但这些运算现在相当便宜,这样的运算很可能被水平依赖性所支配,在新的SSE点积函数中,水平依赖性可能会更优化。您应该进行测试,看看它是否优于双水平加法


我还建议将其与纯标量(或标量SSE)代码进行比较——奇怪的是,它通常更快(通常是因为它在内部是序列化的,但使用寄存器旁路进行了严格的流水线处理,其中特殊的水平指令可能还没有快速路径),除非您运行的是类似SIMT的代码,听起来您并没有(否则,您将使用四个点积)。

我肯定会尝试SSE 4.2。如果您多次这样做(如果性能有问题,我假设您会这样做),您可以使用(1,1,1,1)预加载一个寄存器,然后执行多个点积(my_vec,one_vec)在它上面。是的,它做了一个多余的乘法运算,但这些运算现在相当便宜,这样的运算很可能被水平相关性所支配,这可能在新的SSE点积函数中更为优化。你应该测试一下它是否优于双水平加法

我还建议将其与纯标量(或标量SSE)代码进行比较——奇怪的是,它通常更快(通常是因为它在内部是序列化的,但使用寄存器旁路进行了严格的流水线处理,其中特殊的水平指令可能还没有快速路径),除非您运行的是类似SIMT的代码,听起来您并没有(否则您将使用四点积)。

SSE2 所有四个: r1+r2+r3: 我发现它们的速度与double
HADDPS
差不多(但我没有仔细测量)。

SSE2 所有四个: r1+r2+r3:
我发现它们的速度与double
HADDPS
差不多(但我没有测量得太近)。

一般来说,对于任何类型的向量水平归约,提取/洗牌高半到低,然后垂直相加(或最小/最大/或/和/异或/乘法/无论什么)如果你从大于128位的向量开始,缩小一半,直到你得到128(然后你可以使用这个向量中的一个函数)。除非你需要对结果的所有元素广播,否则你可以考虑做全宽度拖曳。 更宽向量、整数和FP的相关Q&A

  • \uuum128
    \uuuum128d
    此答案(见下文)

  • \uuuu m256d
    对Ryzen 1与Intel进行性能分析(说明为什么
    vextractf128
    vperm2f128
    好得多)

  • \uuum256

  • 单个向量的

  • 数组的点积(不仅仅是3个或4个元素的单个向量):在循环结束后进行垂直mul/add或FMA和hsum,包括有效的hsum。(对于数组的简单求和或其他缩减,使用该模式,但不使用乘法部分,例如add而不是FMA).不要对每个SIMD向量单独执行水平工作;在结束时执行一次

    作为计数
    \u mm256\u cmpeq\u epi8
    匹配的整数示例,再次在整个数组上进行匹配,最后只进行求和(值得特别提及的是,进行一些8位累加,然后加宽8->64位以避免溢出,而不在该点进行完整的求和)

整数

  • \uuu m128i
    32位元素:此答案(见下文)。64位元素应该是显而易见的:只有一个pshufd/paddq步骤

  • \uuum128i
    8位无符号元素:针对
    \umm\u setzero\u si128()
    ,然后对两个qword半部分进行求和(对于较宽的向量,为4或8)。使用SSE2显示128位。 有一个AVX512示例。有一个AVX2
    \uuu m256i
    示例

    (对于有符号字节,可以使用XOR set1(0x80)将SAD之前的值翻转为unsigned,然后从最终的hsum中减去偏差)

  • \u mm\u madd\u epi16
    将set1(1)作为单个uop加宽水平添加构建块,用于窄整数:

  • 带有32位元素的
    \uuuum256i
    \uuuum512i
    。 。对于AVX512,Intel添加了一系列为您执行此操作的“reduce”内联函数(而不是硬件指令),如
    \u mm512\u reduce\u add\u ps
    (以及pd、epi32和epi64)。此外,还添加了reduce\u min/max/mul/和/或。手动执行此操作将导致基本相同的asm

  • 水平最大值(而不是添加):


这个问题的主要答案:主要是浮动和
\uuuu m128
以下是一些基于的Microach指南和指令表进行优化的版本。另请参阅tag wiki。它们在任何CPU上都应该是高效的,没有主要瓶颈
const __m128 t1 = _mm_movehl_ps(v, v);
const __m128 t2 = _mm_add_ps(v, t1);
const __m128 sum = _mm_add_ss(t1, _mm_shuffle_ps(t2, t2, 1));
// Use dummy = a recently-dead variable that vec depends on,
//  so it doesn't introduce a false dependency,
//  and the compiler probably still has it in a register
__m128d highhalf_pd(__m128d dummy, __m128d vec) {
#ifdef __AVX__
    // With 3-operand AVX instructions, don't create an extra dependency on something we don't need anymore.
    (void)dummy;
    return _mm_unpackhi_pd(vec, vec);
#else
    // Without AVX, we can save a MOVAPS with MOVHLPS into a dead register
    __m128 tmp = _mm_castpd_ps(dummy);
    __m128d high = _mm_castps_pd(_mm_movehl_ps(tmp, _mm_castpd_ps(vec)));
    return high;
#endif
}
float hsum_ps_sse1(__m128 v) {                                  // v = [ D C | B A ]
    __m128 shuf   = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 3, 0, 1));  // [ C D | A B ]
    __m128 sums   = _mm_add_ps(v, shuf);      // sums = [ D+C C+D | B+A A+B ]
    shuf          = _mm_movehl_ps(shuf, sums);      //  [   C   D | D+C C+D ]  // let the compiler avoid a mov by reusing shuf
    sums          = _mm_add_ss(sums, shuf);
    return    _mm_cvtss_f32(sums);
}
    # gcc 5.3 -O3:  looks optimal
    movaps  xmm1, xmm0     # I think one movaps is unavoidable, unless we have a 2nd register with known-safe floats in the upper 2 elements
    shufps  xmm1, xmm0, 177
    addps   xmm0, xmm1
    movhlps xmm1, xmm0     # note the reuse of shuf, avoiding a movaps
    addss   xmm0, xmm1

    # clang 3.7.1 -O3:  
    movaps  xmm1, xmm0
    shufps  xmm1, xmm1, 177
    addps   xmm1, xmm0
    movaps  xmm0, xmm1
    shufpd  xmm0, xmm0, 1
    addss   xmm0, xmm1
float hsum_ps_sse3(__m128 v) {
    __m128 shuf = _mm_movehdup_ps(v);        // broadcast elements 3,1 to 2,0
    __m128 sums = _mm_add_ps(v, shuf);
    shuf        = _mm_movehl_ps(shuf, sums); // high half -> low half
    sums        = _mm_add_ss(sums, shuf);
    return        _mm_cvtss_f32(sums);
}

    # gcc 5.3 -O3: perfectly optimal code
    movshdup    xmm1, xmm0
    addps       xmm0, xmm1
    movhlps     xmm1, xmm0
    addss       xmm0, xmm1
#ifdef __AVX__
float hsum256_ps_avx(__m256 v) {
    __m128 vlow  = _mm256_castps256_ps128(v);
    __m128 vhigh = _mm256_extractf128_ps(v, 1); // high 128
           vlow  = _mm_add_ps(vlow, vhigh);     // add the low 128
    return hsum_ps_sse3(vlow);         // and inline the sse3 version, which is optimal for AVX
    // (no wasted instructions, and all of them are the 4B minimum)
}
#endif

 vmovaps xmm1,xmm0               # huh, what the heck gcc?  Just extract to xmm1
 vextractf128 xmm0,ymm0,0x1
 vaddps xmm0,xmm1,xmm0
 vmovshdup xmm1,xmm0
 vaddps xmm0,xmm1,xmm0
 vmovhlps xmm1,xmm1,xmm0
 vaddss xmm0,xmm0,xmm1
 vzeroupper 
 ret
double hsum_pd_sse2(__m128d vd) {                      // v = [ B | A ]
    __m128 undef  = _mm_undefined_ps();                       // don't worry, we only use addSD, never touching the garbage bits with an FP add
    __m128 shuftmp= _mm_movehl_ps(undef, _mm_castpd_ps(vd));  // there is no movhlpd
    __m128d shuf  = _mm_castps_pd(shuftmp);
    return  _mm_cvtsd_f64(_mm_add_sd(vd, shuf));
}

# gcc 5.3.0 -O3
    pxor    xmm1, xmm1          # hopefully when inlined, gcc could pick a register it knew wouldn't cause a false dep problem, and avoid the zeroing
    movhlps xmm1, xmm0
    addsd   xmm0, xmm1


# clang 3.7.1 -O3 again doesn't use movhlps:
    xorpd   xmm2, xmm2          # with  #define _mm_undefined_ps _mm_setzero_ps
    movapd  xmm1, xmm0
    unpckhpd        xmm1, xmm2
    addsd   xmm1, xmm0
    movapd  xmm0, xmm1    # another clang bug: wrong choice of operand order


// This doesn't compile the way it's written
double hsum_pd_scalar_sse2(__m128d vd) {
    double tmp;
    _mm_storeh_pd(&tmp, vd);       // store the high half
    double lo = _mm_cvtsd_f64(vd); // cast the low half
    return lo+tmp;
}

    # gcc 5.3 -O3
    haddpd  xmm0, xmm0   # Lower latency but less throughput than storing to memory

    # ICC13
    movhpd    QWORD PTR [-8+rsp], xmm0    # only needs the store port, not the shuffle unit
    addsd     xmm0, QWORD PTR [-8+rsp]
int hsum_epi32_sse2(__m128i x) {
#ifdef __AVX__
    __m128i hi64  = _mm_unpackhi_epi64(x, x);           // 3-operand non-destructive AVX lets us save a byte without needing a mov
#else
    __m128i hi64  = _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2));
#endif
    __m128i sum64 = _mm_add_epi32(hi64, x);
    __m128i hi32  = _mm_shufflelo_epi16(sum64, _MM_SHUFFLE(1, 0, 3, 2));    // Swap the low two elements
    __m128i sum32 = _mm_add_epi32(sum64, hi32);
    return _mm_cvtsi128_si32(sum32);       // SSE2 movd
    //return _mm_extract_epi32(hl, 0);     // SSE4, even though it compiles to movd instead of a literal pextrd r32,xmm,0
}

    # gcc 5.3 -O3
    pshufd xmm1,xmm0,0x4e
    paddd  xmm0,xmm1
    pshuflw xmm1,xmm0,0x4e
    paddd  xmm0,xmm1
    movd   eax,xmm0

int hsum_epi32_ssse3_slow_smallcode(__m128i x){
    x = _mm_hadd_epi32(x, x);
    x = _mm_hadd_epi32(x, x);
    return _mm_cvtsi128_si32(x);
}