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

E. *_* B. 4 c++ performance simd avx2

我有一个包含 32 个值的array调用。Aunsigned char

我想用__m256这个规则将这些值解包到 4 个变量中,假设我们有一个从 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]
Run Code Online (Sandbox Code Playgroud)

为此,我有以下代码:

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));
Run Code Online (Sandbox Code Playgroud)

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

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

har*_*old 5

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

\n\n

作为对 Intel 的优化,这似乎是可疑的,因为这种转变的 recip.throughput 为 0.5,AND 为 0.33,这比通过 shuffle 得到的 1 更好(具有两个 shuffle 单元的 Intel 处理器不支持 AVX2,因此它们不相关,因此随机播放到 P5)。它的 \xc2\xb5ops 仍然较少,因此在其他代码的上下文中,它可能值得也可能不值得做,具体取决于瓶颈是什么。如果代码的其余部分仅使用 P01(典型的 FP SIMD),则将 \xc2\xb5ops 移动到 P5 可能是一个好主意。

\n\n

在 Ryzen 上,它通常更好,因为矢量移位的吞吐量较低。256bvpsrad生成 2 个 \xc2\xb5ops,它们都必须转到端口 2(然后还有两个 \xc2\xb5ops vpand,但它们可以转到四个 alu 端口中的任何一个),256bvpshufb生成 2 个 \xc2\xb5ops,可以转到端口 1 和 2。另一方面,Ryzen 上的收集非常糟糕,与由此产生的大量 \xc2\xb5ops 相比,这只是噪音。您可以手动收集,但仍然有很多 \xc2\xb5ops,并且它们可能会转到 P12,这使得这种技术很糟糕。

\n\n

总之,我无法告诉您这实际上是否更快,这取决于情况。

\n