首页 > 解决方案 > 将字节相乘以产生 16 位,无需移位

问题描述

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

我知道这些字节总是会产生小于 256 的数字,所以我想避免浪费剩余的 8 位。例如,结果_mm_maddubs_epi16(v1, v2)应该写在rwhere XXis,而不是 where it will be(用 表示__)。

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)

我可以在不改变结果的情况下做到这一点吗?

附言。我没有好的处理器,我仅限于 AVX 指令。

标签: ssesimdavx

解决方案


在您的矢量图中,最高元素是在左边还是在右边?位置是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 个字节的未对齐加载偏移量,以便零在相反的位置结束。


如果您真正想要的是从没有以零开始解包的输入字节开始,我认为您无法避免这种情况。或者,如果您要屏蔽而不是解包(通过使用来节省 shuffle-port 吞吐量_mm_and_si128),您可能需要在某个地方进行转换。但是,您可以移位而不是屏蔽一种方式,使用v1_hi = _mm_slli_epi16(v, 8): 以字粒度左移 8 位将使低字节保持为零。


推荐阅读