【问题标题】:Fastest way to unpack 8bit from 32bit values (__m256i) into __m256 with AVX2使用 AVX2 将 8 位从 32 位值 (__m256i) 解压缩到 __m256 的最快方法
【发布时间】:2018-01-18 23:52:59
【问题描述】:

我有一个名为 Aarray,其中包含 32 个 unsigned char 值。

我想使用此规则将这些值解压缩到 4 个 __m256 变量中,假设我们对来自 A 的所有值都有一个从 0 到 31 的索引,解压缩后的 4 个变量将具有这些值:

B_0 = A[0], A[4],  A[8], A[12], A[16], A[20], A[24], A[28]
B_1 = A[1], A[5],  A[9], A[13], A[17], A[21], A[25], A[29]
B_2 = A[2], A[6], A[10], A[14], A[18], A[22], A[26], A[30]
B_3 = A[3], A[7], A[11], A[15], A[19], A[23], A[27], A[31]

为此,我有以下代码:

const auto mask = _mm256_set1_epi32( 0x000000FF );
...
const auto A_values = _mm256_i32gather_epi32(reinterpret_cast<const int*>(A.data(), A_positions.values_, 4);

// This code bellow is equivalent to B_0 = static_cast<float>((A_value >> 24) & 0x000000FF)
const auto B_0 = _mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srai_epi32(A_values, 24), mask));
const auto B_1 = _mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srai_epi32(A_values, 16), mask));
const auto B_2 = _mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srai_epi32(A_values, 8), mask));
const auto B_3 = _mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srai_epi32(A_values, 0), mask));

这很好用,但我想知道是否有更快的方法来做到这一点,特别是关于我用来检索值的右移和和运算符。

另外,为了澄清起见,我说 array A 的大小为 32,但这不是真的,这个数组包含更多的值,我需要从不同的位置访问它的元素(但总是从块4 uint8_t) 这就是为什么我使用 _mm256_i32gather_epi23 来检索这些值。为了简单起见,我只是在此示例中限制了 array 的大小。

【问题讨论】:

  • 显然可以删除 0 的移位,Clang 会自动执行此操作,但 GCC 和 MSVC 不会。
  • 使用_mm256_srai_epi32(A_values, 24) 不需要用二进制and 0x000000FF 屏蔽高位,因为它们已经是0。
  • @wim 也改成_mm256_srli_epi32 然后
  • @harold 是的,逻辑移位而不是算术移位。我没有注意到_mm256_srai_epi32 中的a
  • 输出的顺序是一成不变的吗?否则,使用_mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(...)) 可能是另一种选择。

标签: c++ performance simd avx2


【解决方案1】:

可以在vpshufb 中组合移位/掩码。当然,这意味着需要担心洗牌掩码,这些掩码必须来自某个地方。如果它们可以留在寄存器中,那没什么大不了的,如果必须加载它们可能会扼杀这种技术。

这似乎是对 Intel 的优化,因为 shift 的 recip.throughput 为 0.5 和 AND 0.33,这比使用 shuffle 获得的 1 更好(具有两个 shuffle 单元的 Intel 处理器不支持AVX2 所以它们不相关,所以随机播放到 P5)。它的微操作仍然更少,因此在其他代码的上下文中,它可能值得做也可能不值得做,这取决于瓶颈是什么。如果其余代码只使用 P01(FP SIMD 的典型情况),则将 µops 移至 P5 可能是个好主意。

在 Ryzen 上它通常更好,因为矢量移位在那里的吞吐量很低。 256b vpsrad 生成 2 个微操作,它们都必须到端口 2(然后还有两个微操作用于 vpand,但它们可以到四个 alu 端口中的任何一个),256b vpshufb 生成 2 个微操作,可以转到端口 1 和 2。另一方面,聚集在 Ryzen 上是如此糟糕,与从中产生的大量 µops 相比,这一切都只是噪音。您可以手动收集,但仍然有很多微操作,它们可能会转到 P12,这使得这种技术很糟糕。

总之,我不能告诉你这是否真的更快,这取决于。

【讨论】:

  • 你好哈罗德,谢谢你的回答,我认为随机播放是改进它的好方法,但我对 SIMD 世界有点陌生,所以,如果可能的话,你能给我举个例子吗我将如何使用它来获得我的移位/掩码操作的相同值?此外,对我的代码进行基准测试时,我发现如果我删除 &amp; detail::mask(在我的真实代码中,掩码变量在我的函数之前的命名空间 detail 内)部分,我的代码运行速度会快 3 倍。正如您在回复中所说,AND 运算符应该非常快。不知道为什么这么慢,也许掩码由于某种原因没有对齐..
  • @E.B.使用_mm256_setr(1, -1, -1, -1, 5, -1, -1, -1, 9, ... 之类的掩码对右移8 的掩码进行随机播放。-1 在输出中为零,其他数字获得具有该索引的字节。这样的速度差异听起来有点奇怪,也许每次都会从内存中重新加载掩码(如果可能,通常应该避免这种情况)
猜你喜欢
  • 2014-08-05
  • 2020-05-23
  • 2015-07-12
  • 2021-01-05
  • 1970-01-01
  • 2019-05-31
  • 1970-01-01
相关资源
最近更新 更多