AVX2中用pshufb做半字节查找表前为何需要掩码操作?
关于AVX2查表法popcount中掩码操作的必要性
结论先说:你设想的省略掩码的写法存在功能错误,会导致置位计数结果不准,原代码里的两条_mm256_and_si256是保证正确性的必要指令,不能删掉。
核心原因:你漏看了vpshufb(_mm256_shuffle_epi8对应硬件指令)的索引规则
你贴的Intel伪代码里其实已经写明了完整逻辑,你只注意到「查表索引只用每个字节的低4位」,但忽略了最高位(bit7)的特殊控制作用:
对每个索引字节,如果其最高位(bit7)为1,对应位置的输出字节会被直接置0,根本不会走查表流程。
只有当索引字节的bit7为0时,硬件才会取低4位作为下标读查找表。至于索引字节的bit4~bit6,硬件确实会直接忽略,不影响结果。
为什么lo分支不能省掩码
lo分支的作用是取每个输入字节的低4位,统计这4位里的置位数量。如果直接把原始加载的vec当索引用:
- 输入字节取值范围是0~255,只要字节值≥0x80,bit7就会置1,直接触发shuffle的零输出逻辑,返回0
- 但此时我们需要的是该字节低4位的计数,比如字节为
0x81时,低4位是0x1,正确计数是1,shuffle却会返回0,最终统计结果会比真实值小。
加了0x0f掩码后,每个字节的高4位被全清,既拿到了正确的低4位索引,也保证所有索引字节的bit7一定是0,shuffle可以正常查表。
为什么hi分支也不能省掩码
hi分支要取每个输入字节的高4位统计置位数。这里有个AVX2的指令集限制:AVX2没有提供8位粒度的逻辑右移指令,所以代码用_mm256_srli_epi16按16位粒度右移4位,这个操作会产生跨字节的脏数据:
- 16位元素由高字节
BH、低字节BL拼接而成,整体右移4位后,BH的低4位会串到新BL字节的高4位,导致右移后的字节高4位不是预期的0 - 当串入的位把字节的bit7置1时(比如
BH的bit3为1时,新BL的bit7就会变成1),同样会触发shuffle的零输出,返回错误结果。
加了掩码后,右移结果的高4位被全清,既消除了16位右移带来的跨字节干扰,拿到每个字节高4位的正确索引,也保证索引字节bit7为0,查表流程正常执行。
补充说明
这两条与操作的指令开销极低,在主流x86 CPU上只需要1个发射周期,完全没有为了省指令而牺牲正确性的必要。等到AVX-512环境下,有了8位粒度的右移指令和更灵活的查表指令后,才可以省去这类掩码操作,但AVX2下必须保留。
内容的提问来源于stack exchange,提问作者phosphorylation
相关产品推荐
相关产品推荐

