计算八个AVX单精度浮点向量的8个水平和

时间:2018-07-10 21:41:40

标签: optimization intrinsics avx low-level

我有8个AVX向量,每个向量包含8个浮点数(总共64个浮点数),我想将每个向量中的元素求和(基本上执行8个水平求和)。

目前,我正在使用以下代码:

__m256 HorizontalSums(__m256 v0, __m256 v1, __m256 v2, __m256 v3, __m256 v4, __m256 v5, __m256 v6, __m256 v7)
{
    // transpose
    const __m256 t0 = _mm256_unpacklo_ps(v0, v1);
    const __m256 t1 = _mm256_unpackhi_ps(v0, v1);
    const __m256 t2 = _mm256_unpacklo_ps(v2, v3);
    const __m256 t3 = _mm256_unpackhi_ps(v2, v3);
    const __m256 t4 = _mm256_unpacklo_ps(v4, v5);
    const __m256 t5 = _mm256_unpackhi_ps(v4, v5);
    const __m256 t6 = _mm256_unpacklo_ps(v6, v7);
    const __m256 t7 = _mm256_unpackhi_ps(v6, v7);

    __m256 v = _mm256_shuffle_ps(t0, t2, 0x4E);
    const __m256 tt0 = _mm256_blend_ps(t0, v, 0xCC);
    const __m256 tt1 = _mm256_blend_ps(t2, v, 0x33);
    v = _mm256_shuffle_ps(t1, t3, 0x4E);
    const __m256 tt2 = _mm256_blend_ps(t1, v, 0xCC);
    const __m256 tt3 = _mm256_blend_ps(t3, v, 0x33);
    v = _mm256_shuffle_ps(t4, t6, 0x4E);
    const __m256 tt4 = _mm256_blend_ps(t4, v, 0xCC);
    const __m256 tt5 = _mm256_blend_ps(t6, v, 0x33);
    v = _mm256_shuffle_ps(t5, t7, 0x4E);
    const __m256 tt6 = _mm256_blend_ps(t5, v, 0xCC);
    const __m256 tt7 = _mm256_blend_ps(t7, v, 0x33);

    // compute sums
    __m256 sum0 = _mm256_add_ps(_mm256_add_ps(tt0, tt1), _mm256_add_ps(tt2, tt3));
    __m256 sum1 = _mm256_add_ps(_mm256_add_ps(tt4, tt5), _mm256_add_ps(tt6, tt7));
    v0 = _mm256_blend_ps(sum0, sum1, 0xF0);
    v1 = _mm256_permute2f128_ps(sum0, sum1, 0x21); // final inter-lane shuffling
    return _mm256_add_ps(v0, v1);
}

如您所见,我只是转置向量并在最后对元素求和。我已经在这里使用了两个技巧:尽可能用_mm256_blend_ps替换_mm256_shuffle_ps,以减轻Intel CPU的端口5压力,同时我在末尾使用_mm256_permute2f128_ps + _mm256_blend_ps来执行通道间改组。

有没有更好(更快)的方法来计算这个?

2 个答案:

答案 0 :(得分:3)

好的,我想我发现了基于(通常很慢)HADD的更快算法:

FUNCTION MoveTower(disk, source, dest, spare):
  IF disk == 0, THEN:
       move disk from source to dest
    ELSE:
       MoveTower(disk - 1, source, spare, dest)   
       move disk from source to dest              
       MoveTower(disk - 1, spare, dest, source)   
  END IF

根据IACA,在Haswell上它的速度快了约8个周期。

答案 1 :(得分:2)

Witek902的solution应该可以正常工作,但是可能 如果select v.* from value v where v.value = (select max(v2.value) from value v2 where trunc(v2.hr, 'HH')= trunc(v.hr, 'HH') ) or v.value = (select max(v2.value) from value v2 where trunc(v2.hr, 'DD')= trunc(v.hr, 'DD') ) ; 被周围的代码频繁调用,则会遭受5号端口的高压。

在Intel Haswell或更高版本上,HorizontalSums指令解码为3个微操作:2个端口5(p5)微操作和 一个用于p1或p01的微操作器(请参阅Agner Fog的说明表)。 函数vhaddps也可以解码为3个微操作,但其中只有一个(随机播放)必须在p5上执行:

sort_of_alternative_hadd_ps

可以替换Witek902的前4个inline __m256 sort_of_alternative_hadd_ps(__m256 x, __m256 y) { __m256 y_hi_x_lo = _mm256_blend_ps(x, y, 0b11001100); /* y7 y6 x5 x4 y3 y2 x1 x0 */ __m256 y_lo_x_hi = _mm256_shuffle_ps(x, y, 0b01001110); /* y5 y4 x7 x6 y1 y0 x3 x2 */ return _mm256_add_ps(y_hi_x_lo, y_lo_x_hi); } 内部函数 answer通过_mm256_hadd_ps()函数。共 需要8条额外的指令来计算水平总和:

sort_of_alternative_hadd_ps

它编译为:

__m256 HorizontalSums_less_p5_pressure(__m256 v0, __m256 v1, __m256 v2, __m256 v3, __m256 v4, __m256 v5, __m256 v6, __m256 v7)
{
    __m256 s01 = sort_of_alternative_hadd_ps(v0, v1);
    __m256 s23 = sort_of_alternative_hadd_ps(v2, v3);
    __m256 s45 = sort_of_alternative_hadd_ps(v4, v5);
    __m256 s67 = sort_of_alternative_hadd_ps(v6, v7);
    __m256 s0123 = _mm256_hadd_ps(s01, s23);
    __m256 s4556 = _mm256_hadd_ps(s45, s67);

    v0 = _mm256_blend_ps(s0123, s4556, 0xF0);
    v1 = _mm256_permute2f128_ps(s0123, s4556, 0x21);
    return _mm256_add_ps(v0, v1);
}

最终,Witek902的HorizontalSums和 CPU将HorizontalSums_less_p5_pressure: vblendps ymm8, ymm0, ymm1, 204 vblendps ymm10, ymm2, ymm3, 204 vshufps ymm0, ymm0, ymm1, 78 vblendps ymm9, ymm4, ymm5, 204 vblendps ymm1, ymm6, ymm7, 204 vshufps ymm2, ymm2, ymm3, 78 vshufps ymm4, ymm4, ymm5, 78 vshufps ymm6, ymm6, ymm7, 78 vaddps ymm0, ymm8, ymm0 vaddps ymm6, ymm6, ymm1 vaddps ymm2, ymm10, ymm2 vaddps ymm4, ymm9, ymm4 vhaddps ymm0, ymm0, ymm2 vhaddps ymm4, ymm4, ymm6 vblendps ymm1, ymm0, ymm4, 240 vperm2f128 ymm0, ymm0, ymm4, 33 vaddps ymm0, ymm1, ymm0 ret 解码为21个微操作, 分别有13个p5微操作和9个p5微操作。

根据周围的代码和实际的微体系结构, 减小的5号端口压力可以改善性能。