C++ 将int64移动到AVX2 m256i向量的高位四字

C++ 将int64移动到AVX2 m256i向量的高位四字,c++,x86-64,simd,intrinsics,avx2,C++,X86 64,Simd,Intrinsics,Avx2,这个问题类似于[1]。然而,我不太明白它是如何解决使用GPR插入ymm的高位四字的问题的。此外,我希望该操作不使用任何中间内存访问 可以用AVX2或更低版本(我没有AVX512)完成吗 [1] 我的回答没有给出一种方法来实现这一点,因为对于屏蔽广播(vpbroadcastq zmm0{k1},rax)来说,没有AVX512F是无法非常有效地实现的。但实际上,使用暂存寄存器并没有那么糟糕,成本与直接混合的vpinsrq差不多 (在Intel上,端口5(vmovq+广播)总共3个uops。2个uop

这个问题类似于[1]。然而,我不太明白它是如何解决使用GPR插入ymm的高位四字的问题的。此外,我希望该操作不使用任何中间内存访问

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

[1] 我的回答没有给出一种方法来实现这一点,因为对于屏蔽广播(
vpbroadcastq zmm0{k1},rax
)来说,没有AVX512F是无法非常有效地实现的。但实际上,使用暂存寄存器并没有那么糟糕,成本与直接混合的
vpinsrq差不多

(在Intel上,端口5(vmovq+广播)总共3个uops。2个uops,可以在任何端口上运行的即时混合。 见)

我用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);
}
其他编译器盲目地广播到完整的YMM,然后混合,即使对于elem=0您可以专门化模板,或者在模板中添加
if()
条件,以进行优化。
例如
splat=(elem?)set1():v保存元素==0的广播。如果需要,您也可以捕获其他优化


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

这在gcc9的当前主干中是固定的,但我不知道是否有一种解决方法可以让早期的gcc生成非愚蠢的代码。通常情况下,
-march=
支持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


对于运行时变量元素位置,混洗仍然有效,但必须创建混合掩码向量,并在右侧元素中设置高位。e、 g.使用
vpmovsxbq
mask[3-elem]
加载
in
alignas(8)int8_t mask[]={0,0,0,-1,0,0,0}。但是
vpblendvb
vblendvd
比直接混合要慢,尤其是在Haswell上,所以尽可能避免这种情况。

用intrinsics更新了我的答案这正是我想要的。谢谢你的详细回答。我有一个跟进问题。对于低阶两个元素,使用这种方法比pinsrq有什么优势?@budchanchao:
vpinsrq
会将高阶两个元素归零
pinsrq
将导致Haswell上的SSE/AVX失速。在Skylake(以及AMD和Xeon Phi)上,YMM元素1的非VEX
pinsrq
可能是最佳选择,但如果没有内联asm,编译器将永远无法发出该命令。对于元件0,
vmovq
+
vpblendd
具有更好的执行端口压力:对于
pinsrq xmm0,rax,0,1p5+1p015,而不是2p5。
# 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
# 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