将字节相乘以产生 16 位,无需移位
Posted
技术标签:
【中文标题】将字节相乘以产生 16 位,无需移位【英文标题】:Multiply bytes to produce 16-bits, without shifting 【发布时间】:2019-01-11 18:53:38 【问题描述】:仍在学习 SIMD 的艺术,我有一个问题:我有两个压缩的 8 位寄存器,我想将它们与 _mm_maddubs_epi16
(pmaddubsw
) 相乘以得到一个 16 位的压缩寄存器。
我知道这些字节会产生总是小于 256 的数字,所以我想避免浪费剩余的 8 位。例如,_mm_maddubs_epi16(v1, v2)
的结果应该将结果写入r
,XX
所在的位置,而不是它的位置(用__
表示)。
v1 (04, 00, 0e, 00, 04, 00, 04, 00, 0a, 00, 0f, 00, 05, 00, 01, 00)
v2 (04, 00, 0e, 00, 04, 00, 04, 00, 0a, 00, 0f, 00, 05, 00, 01, 00)
r (__, XX, __, XX, __, XX, __, XX, __, XX, __, XX, __, XX, __, XX)
我可以不改变结果吗?
PS。我没有很好的处理器,我仅限于 AVX 指令。
【问题讨论】:
什么不能使用轮班? 在您的示例中,所有数字都是<16
和v1==v2
。如果总是这样,您可以使用vpshufb
(_mm256_shuffle_epi8
) 进行表查找。
@Mysticial 不是我不能,而是我尽量避免使用太多操作。
@chtz 不幸的是,我的 CPU 上只有 AVX,没有 AVX2。
如果你没有AVX2,当然可以使用pshufb
(_mm_shuffle_epi8
)。
【参考方案1】:
在你的矢量图中,最高的元素是在左边还是右边? XX
位置是在pmaddubsw
结果的最高有效字节还是最低有效字节中?
从每个单词的高字节输入中获取单词低字节的结果:
使用_mm_mulhi_epu16
,因此您实际上是在执行(v1 << 8) * (v2 << 8) >> 16
,在与输入字相反的字节中产生结果。由于您说乘积严格小于256,您将在每个 16 位字的低字节中得到一个 8 位结果。
(如果您的输入是有符号的,请使用_mm_mulhi_epi16
,但否定结果将被符号扩展为完整的 16 位。)
从低字节的输入中获取单词高字节的结果
您需要更改加载/创建输入之一的方式,而不是
MSB LSB | MSB LSB
v1_lo (00, 04, 00, 0e, 00, 04, 00, 04, 00, 0a, 00, 0f, 00, 05, 00, 01)
element# 15 14 13 12 ... 0
你有这个:(两者都使用英特尔的符号,其中左边的元素是最大的数字,所以向量像_mm_slli_epi128
在图中向左移动字节)。
MSB LSB | MSB LSB
v1_hi (04, 00, 0e, 00, 04, 00, 04, 00, 0a, 00, 0f, 00, 05, 00, 01, 00)
element# 15 14 13 12 ... 0
v2
在每个单词元素的高半部分仍然有其非零字节,只需 _mm_mullo_epi16(v1_hi, v2)
,您将免费获得 (v1 * v2) << 8
。
如果您已经用零解包字节以获得 v1 和 v2,则以另一种方式解包。如果您使用的是pmovzx
(_mm_cvtepu8_epi16
),请切换到使用_mm_unpacklo_epi8(_mm_setzero_si128(), packed_v1 )
。
如果您以这种已经零填充的形式从内存中加载这些向量,请使用 1 个字节的未对齐加载偏移量,以便零在相反的位置结束。
如果您真正想要的是从不以零解包的输入字节开始,我认为您无法避免这种情况。或者,如果您使用屏蔽而不是解包(通过使用 _mm_and_si128
来节省 shuffle-port 吞吐量),您可能需要在某个地方进行转换。但是,您可以使用v1_hi = _mm_slli_epi16(v, 8)
以一种方式移动而不是:使用字粒度左移8 位将使低字节保持为零。
【讨论】:
【参考方案2】:转移v1
或v2
,然后使用_mm_mullo_epi16()
。
可能的 XY 问题?我猜_mm_unpacklo_epi8()
和_mm_packus_epi16()
可能对你有用。
【讨论】:
以上是关于将字节相乘以产生 16 位,无需移位的主要内容,如果未能解决你的问题,请参考以下文章