【问题标题】:fast multiplication of int8 arrays by scalars用标量快速乘以 int8 数组
【发布时间】:2021-11-04 00:44:38
【问题描述】:

我想知道是否有一种将int8 数组相乘的快速方法,

for(i = 0; i < n; ++i)
    z[i] = x * y[i];

我看到Intel intrinsics guide 列出了几个 SIMD 指令,例如 _mm_mulhi_epi16_mm_mullo_epi16,它们对 int16 执行类似的操作。 int8 有没有类似的东西我错过了?

【问题讨论】:

  • 你没有错过它,它真的不存在
  • 对于任意常量,您可以解压缩为 16 位元素。对于常量,您可以将其分解为移位并加减。 (可以使用 _mm_slli_epi32_mm_and_si128 模拟一个常数的 8 位移位,并使用适当的掩码。)
  • 如果您想在完成后对 z[] 进行水平求和,您可以使用 pmaddubsw 进行 8 位乘法 -> 将对水平添加到 16 位累加器中。 (但它是有符号 x 无符号的,因此使用起来很棘手,除非您知道其中一个输入是有符号的。)
  • 好吧,不管它的价值如何,用最高优化编译它会使 gcc 和 clang 反汇编看起来像某种克林贡语言。 godbolt.org/z/saGWh8EnW。在我进行一些认真的基准测试之前,我会三思而后行,然后再用一些手动优化来挑战这个邪恶的混乱。
  • @Lundin:它只是将 2x pmullw => pand / packuswb 解压缩为 16 位元素(扩展为零,因为它会再次截断)(打包回字节,截断,所以无符号饱和度不做任何事情)。至少这是我的假设;这是一种有意义的策略,并且与现有的说明兼容。 Aki 的回答完成了相同的工作,但拆包和重新打包的效率更高。 (链接中的 clang 输出非常复杂,因为它是由 2 个向量展开的。-fno-unroll-loops 便于识别 auto-vec 策略。)

标签: c assembly x86 sse 8-bit


【解决方案1】:

把输入分成low & hi,一个可以

__m128i const kff00ff00 = _mm_set1_epi32(0xff00ff00);
__m128i lo = _mm_mullo_epi16(y, x);
__m128i hi = _mm_mullo_epi16(_mm_and_si128(y, kff00ff00), x);
__m128i z = _mm_blendv_epi8(lo, hi, kff00ff00);

AFAIK,YYyy|YYyy|YYyy|YYyy 的高位 YY 乘以 00xx|00xx|00xx|00xx 不会干扰低 8 位 ??ll,同样YY00|YY00 * 00xx|00xx 的乘积产生正确的 8 位产品在HH00。这两个正确对齐的结果需要混合。

__m128i x = _mm_set1_epi16(scalar_x);__m128i y = _mm_loadu_si128(...);

另一种方法是使用shufb 计算LutLo[y &amp; 15] + LutHi[y &gt;&gt; 4],不幸的是,_mm_and_si128(_mm_srli_epi16(y,4),_mm_set1_epi8(15)) 也必须模拟移位。

【讨论】:

  • 哦,不错,如果您想截断而不是使用_mm_packus_epi16 (packuswb),最好在 16 位元素中拆分高/低,而不是解压缩低/高。这里的x 向量是_mm_set1_epi16(scalar_x),而y 是来自数组的负载。 (在修改错字时为未来的读者添加了这一点)
  • 这真的有效吗?我得到了一些奇怪的结果
  • 我认为乘法版本需要展开 3 倍,因为乘法的延迟为 5。对于 shufb 方法,展开 2 倍应该足以达到最佳效果吞吐量。
  • @harold:谢谢,两个乘法都必须是 mullo,我首先对其进行了过度设计。我会仔细检查。
  • @AkiSuihkonen:询问者的用例是独立工作。乱序 exec 可以隐藏 pmullw 迭代之间的延迟,因为它不是循环承载的 dep 链的一部分,因此展开并不重要。总体吞吐量,而不是延迟,是这方面的关键因素。
猜你喜欢
  • 2015-08-21
  • 2016-08-12
  • 2011-04-10
  • 1970-01-01
  • 1970-01-01
  • 2013-11-11
  • 1970-01-01
  • 2021-12-05
  • 2010-12-19
相关资源
最近更新 更多