【问题标题】:Construct a 64 bit mask register from four 16 bit ones从四个 16 位 1 构造一个 64 位掩码寄存器
【发布时间】:2021-09-19 10:41:15
【问题描述】:

从四个__mmask16 中得到__mmask64 的最佳方法是什么?我只想连接它们。网上好像没找到解决办法。

【问题讨论】:

    标签: x86-64 simd avx512


    【解决方案1】:

    您可以将 __mmask16__mmask64 视为 16 位和 64 位整数,例如

    __mmask64 set_mask64(__mmask16 m0, __mmask16 m1, __mmask16 m2, __mmask16 m3)
    {
        return (((__mmask64)m0) << 0)
             | (((__mmask64)m1) << 16)
             | (((__mmask64)m2) << 32)
             | (((__mmask64)m3) << 48);
    }
    

    或许:

    __mmask64 set_mask64(__mmask16 m0, __mmask16 m1, __mmask16 m2, __mmask16 m3)
    {
        return (__mmask64)_mm_set_pi16(m0, m1, m2, m3);
    }
    

    以上都使用标量/SSE 代码。使用 AVX512 掩码内部函数会更有效(请参阅@Peter's answer 以获得更好的解决方案)。

    【讨论】:

    • 请注意,代码生成是纯标量整数,因为__mask16 实际上只是unsigned short 的类型定义。要真正让编译器处理k 寄存器中的值,您需要为其提供来自vector->mask 内在函数的输入,并可能将结果用作某些指令的掩码,也许只是mask->vector @ 987654330@
    • 另外,_mm_set_pi16 是一个 MMX 内在函数,尽管您从 __m64__mmask64 的转换确实可以在 GCC 和 clang 中使用,甚至没有警告。尽管 clang 确实在 XMM reg 中使用了 vpinsrw,但最终还是使用了 vmovq rax, xmm0。显然,x64 MSVC 根本无法识别该内在函数(可能是因为 MS 出于某种原因决定在 64 位代码中删除对 MMX 的编译器支持。)对于 32 位 MSVC,它不知道如何进行转换: godbolt.org/z/48cTxeKdE
    • (使用_mm512_kunpackw_mm512_kunpackd添加了答案)
    【解决方案2】:

    AVX-512 具有用于连接两个掩码寄存器的硬件指令,例如 2x kunpckwd instructions 和一个 kunpckdq 就可以做到这一点。

    (每条指令在 SKX 和 Ice Lake 上是 4 个周期延迟,仅端口 5。https://uops.info。但至少第一步中的 2 个独立的可以大部分重叠,相隔一个周期开始,受端口竞争限制5. 但无论如何,它们不会立即全部准备好,如果编译器安排生成 4 个掩码的指令,那么应该首先准备好一对,这样它就可以开始了。)

    // compiles nicely with GCC/clang/ICC.  Current MSVC has major pessimizations
    inline
    __mmask64 set_mask64_kunpck(__mmask16 m0, __mmask16 m1, __mmask16 m2, __mmask16 m3)
    {
        __mmask32 md0 = _mm512_kunpackw(m1, m0);  // hi, lo
        __mmask32 md1 = _mm512_kunpackw(m3, m2);
        __mmask64 mq = _mm512_kunpackd(md1, md0);
        return mq;
    }
    

    如果您的 __mask16 值实际上在 k 寄存器中,那是您最好的选择,如果它们是像 _mm512_cmple_epu32_mask 这样的 AVX-512 比较/测试内在函数的结果,编译器将拥有它们。如果它们来自您之前生成的数组,最好将它们与普通的标量内容结合起来(参见 Paul 的回答),而不是使用 kmov 慢慢地将它们放入掩码寄存器中。 kmov k, mem 是前端的 3 个微指令,带有标量整数负载和一个 kmov k, reg 后端微指令,加上一个额外的前端微指令,没有明显的原因。


    __mmask16 只是 unsigned short 的 typedef(在 gcc/clang/ICC/MSVC 中),因此您可以像整数一样简单地操作它,编译器将根据需要使用 kmov . (如果你不小心,这可能会导致代码效率很低,不幸的是,当前的编译器还不够聪明,无法将 shift/OR 函数编译为使用kunpckwd。)

    个像 unsigned int _cvtmask16_u32 (__mmask16 a) 这样的内在函数,但它们对于当前将 __mmask16 实现为 unsigned short 的编译器是可选的。


    要查看__mmask16 值从k 寄存器开始的情况的编译器输出,有必要编写一个使用内在函数创建掩码值的测试函数。 (或使用内联 asm 约束。)标准 x86-64 调用约定将 __mmask16 处理为标量整数,因此作为函数 arg,它已经在整数寄存器中,而不是 k 寄存器中。

    __mmask64 test(__m256i v0, __m256i v1, __m256i v2, __m256i v3)
    {
        __mmask16 m0 = _mm256_movepi16_mask(v0);  // clang can optimize _mm_movepi8_mask into pmovmskb eax, xmm avoiding k regs
        __mmask16 m1 = _mm256_movepi16_mask(v1);
        __mmask16 m2 = _mm256_movepi16_mask(v2);
        __mmask16 m3 = _mm256_movepi16_mask(v3);
    
        //return set_mask64_mmx(m0,m1,m2,m3);
        //return set_mask64_scalar(m0,m1,m2,m3);
        return set_mask64_kunpck(m0,m1,m2,m3);
    }
    

    使用 GCC 和 clang,编译为 (Godbolt):

    # gcc 11.1  -O3 -march=skylake-avx512
    test(long long __vector(4), long long __vector(4), long long __vector(4), long long __vector(4)):
            vpmovw2m        k3, ymm0
            vpmovw2m        k1, ymm1
            vpmovw2m        k2, ymm2
            vpmovw2m        k0, ymm3     # create masks
    
            kunpckwd        k1, k1, k3
            kunpckwd        k0, k0, k2
            kunpckdq        k4, k0, k1   # combine masks
    
            kmovq   rax, k4              # use mask, in this case by returning as integer
            ret
    

    例如,我本可以将最终掩码结果用于两个输入之间的内部混合,但编译器并没有尝试通过执行 4x kmov(也只有 1 个端口)来避免 kunpck

    MSVC 19.29 -O2 -Gv -arch:AVX512 做得很差,将每个掩码提取为内在函数之间的标量整数 reg。喜欢

    MSVC 19.29
            kmovw   ax, k1
            movzx   edx, ax
            ...
            kmovd   k3, edx
    

    这非常愚蠢,甚至没有使用kmovw eax, k1 将零扩展为 32 位寄存器,更不用说没有意识到下一个 kunpck 无论如何只关心其输入的低部分,所以有根本不需要 kmov 数据到/从整数寄存器。后来,它甚至使用了这个,显然没有意识到kmovd 写一个 32 位寄存器零扩展到 64 位寄存器。 (公平地说,GCC 在其 __builtin_popcount 内在函数周围有一些愚蠢的错过优化。)

    ; MSVC 19.29
            kmovd   ecx, k2
            mov     ecx, ecx
            kmovq   k1, rcx
    

    kunpck 内在函数确实有奇怪的原型,输入与输出一样宽,例如

    __mmask32 _mm512_kunpackw (__mmask32 a, __mmask32 b)
    

    因此,这可能是在欺骗 MSVC 手动执行 uint16_t -> uint32_t 通过标量和返回的转换,因为它显然不知道 vpmovw2m k3, ymm0 已经零扩展到完整的 k3 .

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-21
      • 2023-03-05
      • 1970-01-01
      • 1970-01-01
      • 2014-12-17
      • 2012-01-24
      • 1970-01-01
      • 2012-04-11
      相关资源
      最近更新 更多