使用 AVX2 将 8 位从 32 位值 (__m256i) 解压缩到__m256的最快方法

Fastest way to unpack 8bit from 32bit values (__m256i) into __m256 with AVX2

本文关键字:m256 解压缩 方法 m256i 位从 位值 使用 AVX2      更新时间:2023-10-16

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

我想用这个规则将这些值解压缩在 4 个__m256变量中,假设我们有一个从 0 到 31 的索引,关于A中的所有值,解压缩的 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));

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

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

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

这似乎是对英特尔的优化,因为这种转变的 recip.throughput 为 0.5 和 AND 0.33,这比洗牌得到的 1 要好(具有两个洗牌单元的英特尔处理器不支持 AVX2,因此它们不相关,因此洗牌转到 P5(。它仍然较少的μops,因此在其他代码的上下文中,它可能值得也可能不值得做,这取决于瓶颈是什么。如果代码的其余部分只使用 P01(FP SIMD 的典型特征(,则将 μops 移动到 P5 可能是个好主意。

在锐龙上,它通常更好,因为矢量移位在那里的吞吐量较低。256bvpsrad生成 2 μops,两者都必须转到端口 2(然后vpand还有两个 μop,但它们可以转到四个 alu 端口中的任何一个(,256bvpshufb生成 2 μops 可以转到端口 1 和 2。另一方面,Ryzen 上的聚集是如此糟糕,以至于与由此产生的大量微运算相比,这一切都只是噪音。你可以手动收集,但它仍然有很多μops,他们可能会去P12,这使得这种技术很糟糕。

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