首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >将int64_t移动到AVX2 __m256i向量的高四字

将int64_t移动到AVX2 __m256i向量的高四字
EN

Stack Overflow用户
提问于 2019-01-05 01:40:01
回答 1查看 494关注 0票数 2

这个问题类似于1,但是我不太明白它是如何解决使用探地雷达插入高四字的问题的。另外,我希望操作不使用任何中间内存访问。

它能用AVX2或更低的(我没有AVX512)来完成吗?

1

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-01-05 01:54:04

我的回答关于关联问题没有给出一种方法来做到这一点,因为没有AVX512F就无法很有效地进行蒙面广播(vpbroadcastq zmm0{k1}, rax)。但实际上,使用划痕寄存器并没有那么糟糕,它的成本与vpinsrq +直接混合的成本差不多。

(以英特尔为例,总计3次。端口5的2个uop (vmovq +广播),以及可以在任何端口上运行的立即混合。见https://agner.org/optimize/)。

我用asm更新了我的答案。在C++中,在英特尔的本质中,你会做如下的事情:

代码语言:javascript
运行
复制
#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种可能的元素位置都进行了非常高效的编译,这表明了它的洗牌优化器是多么的好。它利用了所有的特殊情况。而且,作为一个额外的,它评论它的asm,以告诉您哪些元素来自哪里混合和洗牌。

(v,+newval); } __m256i+merge2(__m256i+v,+int64_t+newval)+{ ++++return+merge_epi64<2>(v,+newval); } __m256i+merge1(__m256i+v,+int64_t+newval)+{ ++++return+merge_epi64<1>(v,+newval); } __m256i+merge0(__m256i+v,+int64_t+newval)+{ ++++return+merge_epi64<0>(v,+newval); } '),l:'5',n:'0',o:'C+++source+#1',t:'0')),k:37.06327601492114,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:clang700,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'1',trim:'1'),fontScale:1.0749542399999998,lang:c++,libs:!(),options:'-O3+-Wall+-march=haswell',source:1),l:'5',n:'0',o:'x86-64+clang+7.0.0+(Editor+#1,+Compiler+#1)+C++',t:'0')),k:32.48462940134176,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((g:!((h:compiler,i:(compiler:gsnapshot,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'1',trim:'1'),fontScale:1.0749542399999998,lang:c++,libs:!(),options:'-std=gnu++17+-O3+-Wall+-march=haswell',source:1),l:'5',n:'0',o:'x86-64+gcc+(trunk)+(Editor+#1,+Compiler+#2)+C++',t:'0')),header:(),k:30.452094583737093,l:'4',m:50,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:vcpp_v19_16_x64,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'1',trim:'1'),fontScale:1.0749542399999998,lang:c++,libs:!(),options:'-Ox+-arch:AVX2+-Gv',source:1),l:'5',n:'0',o:'x64+msvc+v19.16+(Editor+#1,+Compiler+#3)+C++',t:'0')),header:(),l:'4',m:50,n:'0',o:'',s:0,t:'0')),k:30.452094583737093,l:'3',n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)">从戈德波特编译器浏览器,一些测试函数,以查看args在regs中发生了什么。

代码语言:javascript
运行
复制
__m256i merge3(__m256i v, int64_t newval) {
    return merge_epi64<3> (v, newval);
}
// and so on for 2..0

代码语言:javascript
运行
复制
# 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选择存储/重新加载。

代码语言:javascript
运行
复制
# 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上,所以如果可能的话,不要这样做。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54048226

复制
相关文章

相似问题

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