将int64_t移至AVX2 __m256i向量的高四倍数

时间:2019-01-05 01:40:02

标签: c++ x86-64 simd intrinsics avx2

这个问题类似于[1]。但是,我不太了解它如何解决使用GPR插入到高倍数的ymm的问题。另外,我希望该操作不使用任何中间内存访问。

可以使用AVX2或更低版本(我没有AVX512)吗?

[1] How to move double in %rax into particular qword position on %ymm or %zmm? (Kaby Lake or later)

1 个答案:

答案 0 :(得分:3)

我的答案on the linked question没有显示出解决方法,因为如果没有AVX512F进行掩蔽广播(vpbroadcastq zmm0{k1}, rax),就无法高效地完成。但是使用暂存寄存器实际上并没有那么糟糕,其成本与vpinsrq +立即混合相同。

(在Intel上,总计3 ups。端口5(vmovq +广播)为2 ups,并且可以在任何端口上运行的即时混合。  参见https://agner.org/optimize/)。

为此,我在那里用asm更新了答案。在具有英特尔内部函数的C ++中,您将执行以下操作:

#include <immintrin.h>
#include <stdint.h>

// integer version.  An FP version would still use _mm256_set1_epi64x, then a cast
template<unsigned elem>
static inline
__m256i merge_epi64(__m256i v, int64_t newval)
{
    static_assert(elem <= 3, "a __m256i only has 4 qword elements");

    __m256i splat = _mm256_set1_epi64x(newval);

    constexpr unsigned dword_blendmask = 0b11 << (elem*2);  // vpblendd uses 2 bits per qword
    return  _mm256_blend_epi32(v, splat, dword_blendmask);
}

Clang可以针对所有4个可能的元素位置几乎完全有效地进行编译,这确实表明了shuffle优化器的出色表现。它利用了所有特殊情况。作为奖励,它会评论其组件以向您显示哪些元素来自混合和混洗中的位置。

From the Godbolt compiler explorer ,一些测试功能可以查看reg中的arg会发生什么。

__m256i merge3(__m256i v, int64_t newval) {
    return merge_epi64<3> (v, newval);
}
// and so on for 2..0

# clang7.0 -O3 -march=haswell
merge3(long long __vector(4), long):
    vmovq   xmm1, rdi
    vpbroadcastq    ymm1, xmm1
    vpblendd        ymm0, ymm0, ymm1, 192 # ymm0 = ymm0[0,1,2,3,4,5],ymm1[6,7]
                      # 192 = 0xC0 = 0b11000000
    ret

merge2(long long __vector(4), long):
    vmovq   xmm1, rdi
    vinserti128     ymm1, ymm0, xmm1, 1          # Runs on more ports than vbroadcast on AMD Ryzen
        #  But it introduced a dependency on  v (ymm0) before the blend for no reason, for the low half of ymm1.  Could have used xmm1, xmm1.
    vpblendd        ymm0, ymm0, ymm1, 48 # ymm0 = ymm0[0,1,2,3],ymm1[4,5],ymm0[6,7]
    ret

merge1(long long __vector(4), long):
    vmovq   xmm1, rdi
    vpbroadcastq    xmm1, xmm1           # only an *XMM* broadcast, 1c latency instead of 3.
    vpblendd        ymm0, ymm0, ymm1, 12 # ymm0 = ymm0[0,1],ymm1[2,3],ymm0[4,5,6,7]
    ret

merge0(long long __vector(4), long):
    vmovq   xmm1, rdi
           # broadcast optimized away, newval is already in the low element
    vpblendd        ymm0, ymm0, ymm1, 3 # ymm0 = ymm1[0,1],ymm0[2,3,4,5,6,7]
    ret

其他编译器盲目广播到完整的YMM,然后混合,即使对于elem = 0。 您可以对模板进行专业化处理,或在模板中添加if()个可以优化的条件。例如splat = (elem?) set1() : v;将广播保存为elem == 0。如果需要,您也可以捕获其他优化。


GCC 8.x和更早的版本使用通常不好的方式广播整数:它们存储/重新加载。这样可以避免使用任何ALU改组端口,因为广播负载在Intel CPU上是免费的,但是它将存储转发延迟引入了从整数到最终向量结果的链中。

这在gcc9的当前主干中已解决,但是我不知道是否有解决方法可以使早期的gcc获得非愚蠢的代码源。通常,-march=<an intel uarch>倾向于使用ALU而不是整数->向量的存储/重新加载,反之亦然,但是在这种情况下,成本模型仍然使用-march=haswell选择存储/重新加载。

# gcc8.2 -O3 -march=haswell
merge0(long long __vector(4), long):
    push    rbp
    mov     rbp, rsp
    and     rsp, -32          # align the stack even though no YMM is spilled/loaded
    mov     QWORD PTR [rsp-8], rdi
    vpbroadcastq    ymm1, QWORD PTR [rsp-8]   # 1 uop on Intel
    vpblendd        ymm0, ymm0, ymm1, 3
    leave
    ret

; GCC trunk: g++ (GCC-Explorer-Build) 9.0.0 20190103 (experimental)
; MSVC and ICC do this, too.  (For MSVC, make sure to compile with -arch:AVX2)
merge0(long long __vector(4), long):
    vmovq   xmm2, rdi
    vpbroadcastq    ymm1, xmm2
    vpblendd        ymm0, ymm0, ymm1, 3
    ret

对于运行时变量元素位置,混洗仍然有效,但是您必须创建一个混合掩码矢量,并在右侧元素中设置高位。例如从vpmovsxbq中的mask[3-elem]加载了alignas(8) int8_t mask[] = { 0,0,0,-1,0,0,0 };的邮件。但是vpblendvbvblendvpd比立即混合要慢,尤其是在Haswell上,因此请尽可能避免这种情况。