【发布时间】:2021-09-19 10:41:15
【问题描述】:
从四个__mmask16 中得到__mmask64 的最佳方法是什么?我只想连接它们。网上好像没找到解决办法。
【问题讨论】:
从四个__mmask16 中得到__mmask64 的最佳方法是什么?我只想连接它们。网上好像没找到解决办法。
【问题讨论】:
您可以将 __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添加了答案)
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 .
【讨论】: