AVX512编程:mmask32能否强制转换为mmask16/mmask8
AVX512掩码指令实现10位打包流转16位格式的尾部处理问题
应用背景
当前研究方向为针对尾部待处理元素的掩码加载、存储指令使用方案,落地场景为打包10位数据流到16位格式的转换:每加载5字节输入,对应存储4个short类型输出,处理过程需要用到不同位宽的掩码。
主循环实现已经验证不存在问题,尾部处理阶段最多剩余19字节输入、15个short类型输出,原计划使用128位向量最多分2次迭代完成尾部处理,初始实现代码如下:
#include <immintrin.h> #include <stddef.h> #include <stdint.h> void convert(uint16_t* out, ptrdiff_t n, const uint8_t* in) { uint16_t* const out_end = out + n; for(uint16_t* out32_end = out + (n & -32); out < out32_end; in += 40, out += 32) { /* * 此处插入ZMM向量实现的主循环逻辑 */ } if(out_end - out >= 16) { /* * 此处插入YMM向量实现的半长度迭代逻辑 */ in += 20; out += 16; } // 剩余最多19字节输入,最多15个short输出 const unsigned out_remain = out_end - out; const unsigned in_remain = (out_remain * 10 + 7) / 8; unsigned in_mask = (1 << in_remain) - 1; unsigned out_mask = (1 << out_remain) - 1; while(out_mask) { __mmask16 load_mask = _cvtu32_mask16(in_mask); __m128i packed = _mm_maskz_loadu_epi8(load_mask, in); /* 此处插入计算逻辑,不需要额外掩码 */ __mmask8 store_mask = _cvtu32_mask8(out_mask); _mm_mask_storeu_epi16(out, store_mask, packed); in += 10; out += 8; in_mask >>= 10; out_mask >>= 8; } }
代码使用的编译选项为-O3 -mavx2 -mavx512f -mavx512bw -mavx512vl -mavx512dq。
初始方案的缺陷
初始方案根据剩余元素数生成位掩码,处理时逐次对掩码做移位操作,存在两个问题:
- 每次迭代都从通用寄存器重设掩码值,没有使用
kshift系列掩码移位指令,存在不必要的寄存器往返开销 _cvtu32_mask8(对应kmovb指令)是代码中唯一依赖AVX512DQ指令集的 intrinsic,仅为这一条指令就限制可支持的硬件平台,性价比极低
优化思路与编译结果
尝试确认是否可以将__mmask32类型强制转换为__mmask16和__mmask8类型:如果该方式可行,只需要从通用寄存器加载一次掩码,后续移位操作全部在掩码寄存器内完成即可,优化思路的示例代码如下:
__mmask32 load_mask = _cvtu32_mask32(in_mask); __mmask32 store_mask = _cvtu32_mask32(out_mask); while(out < out_end) { __m128i packed = _mm_maskz_loadu_epi8((__mmask16) load_mask, in); /* 此处插入计算逻辑,不需要额外掩码 */ _mm_mask_storeu_epi16(out, (__mmask8) store_mask, packed); load_mask = _kshiftri_mask32(load_mask, 10); store_mask = _kshiftri_mask32(store_mask, 8); in += 10; out += 8; }
实际测试发现,GCC可以正常识别该写法模式,生成质量很高的目标代码,但Clang与MSVC会无理由地在掩码寄存器与通用寄存器之间来回搬运数据,生成的代码质量较差。
内容的提问来源于stack exchange,提问作者Homer512
相关产品推荐
相关产品推荐

