字节相乘产生 16 位,无需移位

Multiply bytes to produce 16-bits, without shifting

仍在学习 SIMD 的艺术,我有一个问题:我有两个打包的 8 位寄存器,我想将它们与 _mm_maddubs_epi16 (pmaddubsw) 相乘以获得16 位压缩寄存器。

我知道这些字节会总是产生一个小于 256 的数字,所以我想避免浪费剩余的 8 位。例如,_mm_maddubs_epi16(v1, v2) 的结果应该将结果写入 rXX 所在的位置,而不是它应该在的位置(用 __ 表示)。

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 指令。

Shift v1v2 然后使用 _mm_mullo_epi16().

可能是 XY 问题?我的猜测是 _mm_unpacklo_epi8()_mm_packus_epi16() 可能对您有用。

在你的矢量图中,最高的元素是在左边还是右边? 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

你有这个:(都使用 Intel 的表示法,其中左边的元素是最大的数字,所以向量像 _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 来节省洗牌端口吞吐量),您可能需要在某个地方换班。但是,您可以使用 v1_hi = _mm_slli_epi16(v, 8) 以一种方式移动 而不是 屏蔽:左移 8 字粒度将使低字节归零。