首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >用AVX512或AVX2计算所有32位填充整数之和的最快方法

用AVX512或AVX2计算所有32位填充整数之和的最快方法
EN

Stack Overflow用户
提问于 2020-02-07 07:08:13
回答 1查看 3.2K关注 0票数 6

我正在寻找一种计算__m256i__m512i中所有32位整数的最优方法。为了计算n元素的和,我经常使用log2(n) vpadddvpermd函数,然后提取最终结果。然而,我认为这不是最好的选择。

编辑:最佳/最优的速度/周期减少。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-02-07 08:26:54

相关:如果您正在寻找不存在的_mm512_reduce_add_epu8,请将带AVX本质的m512i vpsadbw看作qword中的hsum比洗牌更有效。

如果没有AVX512,请参阅下面的hsum_8x32(__m256i),以获得没有Intel的reduce_add助手函数的AVX2。无论如何,reduce_add不一定使用AVX512进行优化编译。

int _mm512_reduce_add_epi32(__m512i)中有一个immintrin.h内联函数。你最好用它。(它编译成洗牌和添加指令,但比我下面描述的vpermd更高效。) AVX512没有为水平和引入任何新的硬件支持,只是这个新的辅助函数。只要有可能,仍然需要避免或退出循环。

GCC 9.2 -O3 -march=skylake-avx512编译了一个包装器,调用它如下:

代码语言:javascript
运行
复制
        vextracti64x4   ymm1, zmm0, 0x1
        vpaddd  ymm1, ymm1, ymm0
        vextracti64x2   xmm0, ymm1, 0x1   # silly compiler, vextracti128 would be shorter
        vpaddd  xmm1, xmm0, xmm1
        vpshufd xmm0, xmm1, 78
        vpaddd  xmm0, xmm0, xmm1

        vmovd   edx, xmm0
        vpextrd eax, xmm0, 1              # 2x xmm->integer to feed scalar add.
        add     eax, edx
        ret

提取两次以输入标量添加是有问题的;它需要p0和p5的uop,因此它相当于常规的洗牌+ movd

Clang没有这样做;它再做一步洗牌/ SIMD添加,将vmovd的标量降到一个标量。关于两者的perf分析,见下文。

有一个VPHADDD,但是您不应该在两个输入相同的情况下使用它。(除非您正在优化代码大小而不是速度)。它可能是有用的转置和多向量,从而产生一些向量的结果.您可以通过向phadd提供两个不同的输入来做到这一点。(除了256位和512位的数据变得杂乱无章,因为vphadd仍然只在车道上。)

是的,您需要log2(vector_width) vpaddd instructions.洗牌(所以这不是很有效;避免内部循环中的水平和)。例如,垂直累积直到循环结束)。

所有SSE / AVX / AVX512的总体策略

您想要依次从512 __m128i 256缩小到256 -> 128,然后在 -> 中进行洗牌,直到降到一个标量元素为止。一些未来的AMD CPU可能会将512位指令解码成两个256位的uop,因此减少宽度是一个很大的胜利。而较窄的指令,想必只需要稍微少一点的能量。

vpermd**.**您的洗牌可以立即控制操作数,而不是用于的向量,例如VEXTRACTI32x8vextracti128vpshufd。(或用vpunpckhqdq保存直接常量的代码大小。)

请参见最快的方法做水平SSE向量和(或其他缩减) (我的答案还包括一些整数版本)。

此一般策略适用于所有元素类型:浮点数、双值和任意大小的整数。

特例:

  • 8位整数:从vpsadbw开始,效率更高,避免溢出,但对于64位整数则继续。
  • 16位整数:从pmaddwd开始扩展到32 (_mm256_madd_epi16 with set1_epi16(1)):SIMD:累加相邻对 --即使您不关心避免溢出的好处,也可以减少uop,但在Zen2之前的SIMD:累加相邻对除外,在Zen2之前,256位指令至少要花费2 uop。但是,对于32位整数,则继续。

32位整数可以像这样手动完成,SSE2函数在还原为__m128i之后由AVX2函数调用,然后由AVX512函数在还原为__m256i之后调用。当然,在实际操作中,这些呼叫将是内联的。

代码语言:javascript
运行
复制
#include <immintrin.h>
#include <stdint.h>

// from my earlier answer, with tuning for non-AVX CPUs removed
// static inline
uint32_t hsum_epi32_avx(__m128i x)
{
    __m128i hi64  = _mm_unpackhi_epi64(x, x);           // 3-operand non-destructive AVX lets us save a byte without needing a movdqa
    __m128i sum64 = _mm_add_epi32(hi64, x);
    __m128i hi32  = _mm_shuffle_epi32(sum64, _MM_SHUFFLE(2, 3, 0, 1));    // Swap the low two elements
    __m128i sum32 = _mm_add_epi32(sum64, hi32);
    return _mm_cvtsi128_si32(sum32);       // movd
}

// only needs AVX2
uint32_t hsum_8x32(__m256i v)
{
    __m128i sum128 = _mm_add_epi32( 
                 _mm256_castsi256_si128(v),
                 _mm256_extracti128_si256(v, 1)); // silly GCC uses a longer AXV512VL instruction if AVX512 is enabled :/
    return hsum_epi32_avx(sum128);
}

// AVX512
uint32_t hsum_16x32(__m512i v)
{
    __m256i sum256 = _mm256_add_epi32( 
                 _mm512_castsi512_si256(v),  // low half
                 _mm512_extracti64x4_epi64(v, 1));  // high half.  AVX512F.  32x8 version is AVX512DQ
    return hsum_8x32(sum256);
}

注意,这使用__m256i hsum作为__m512i的构建块;首先在车道上操作没有什么好处。

好吧,这可能是一个非常小的优势:在车道上的洗牌比过车道有更低的延迟,所以他们可以提前执行两个周期,提前离开RS,同样的,从ROB稍早的时候退休。但是,即使你这样做了,高延迟的洗牌也只是在几个指令之后才出现。因此,如果hsum在关键路径上(阻塞退休),您可能会得到一些独立的指令进入后端2周期。

但是缩短到更窄的矢量宽度通常是好的,如果你不马上做更多的512位工作的话,也许可以更快地从系统中取出512位的uop,这样CPU就可以重新激活端口1上的SIMD执行单元。

使用GCC9.2 论哥德波特-O3 -march=skylake-avx512编译到这些指令中

代码语言:javascript
运行
复制
hsum_16x32(long long __vector(8)):
        vextracti64x4   ymm1, zmm0, 0x1
        vpaddd  ymm0, ymm1, ymm0
        vextracti64x2   xmm1, ymm0, 0x1   # silly compiler uses a longer EVEX instruction when its available (AVX512VL)
        vpaddd  xmm0, xmm0, xmm1
        vpunpckhqdq     xmm1, xmm0, xmm0
        vpaddd  xmm0, xmm0, xmm1
        vpshufd xmm1, xmm0, 177
        vpaddd  xmm0, xmm1, xmm0
        vmovd   eax, xmm0
        ret

_mm512_reduce_add_epi32 P.S.:用来自https://uops.info/和/或阿格纳福格(氏)指令表的数据分析GCC的与clang的(相当于我的版本)

在内联到调用者进行结果处理之后,可以使用lea eax, [rax + rdx + 123]或其他方法进行优化,比如添加常量。

但除此之外,似乎几乎总是比我在Skylake上的实现结束时的混乱/ vpadd / vmovd更糟糕:

  • 总数:减少: 4.我的:3
  • 端口:减少: 2p0,p5 (vpextrd的一部分),p0156 (标量add)
  • 端口: p5,p015 (vpadd on SKX),p0 (vmod)

假定没有资源冲突,4个周期的延迟是相等的:

  • 洗牌1循环-> SIMD加1循环-> vmovd 2循环
  • -> 3循环(与2周期vmovd并行)增加1周期。
票数 13
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60108658

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档