高效(在Ryzen上)将__m256的奇数元素提取到__m128中的方法?

Ser*_*tch 6 c++ sse x86-64 vectorization avx2

是否有一种内在或另一种有效的方法将AVX寄存器的64位组件的高/低32位组件重新打包到SSE寄存器中?使用AVX2的解决方案还可以.

到目前为止,我正在使用以下代码,但是分析器说它在Ryzen 1800X上很慢:

// Global constant
const __m256i gHigh32Permute = _mm256_set_epi32(0, 0, 0, 0, 7, 5, 3, 1);

// ...

// function code
__m256i x = /* computed here */;
const __m128i high32 = _mm256_castsi256_si128(_mm256_permutevar8x32_epi32(x),
  gHigh32Permute); // This seems to take 3 cycles
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 5

在英特尔上,您的代码将是最佳的。一个 1-uop 指令是最好的。(除非您可能希望使用vpermps以避免 int / FP 旁路延迟的任何风险,如果您的输入向量是由pd指令而不是负载或其他东西创建的。使用 FP shuffle 的结果作为整数指令的输入通常很好在英特尔上,但我不太确定将 FP 指令的结果提供给整数洗牌。)

虽然如果针对 Intel 进行调优,您可能会尝试更改周围的代码,以便您可以混入每个 128b 通道的底部 64 位,以避免使用跨通道混洗。(然后你可以只使用vshufps ymm, 或者如果调整 KNL,vpermilps因为 2-inputvshufps速度较慢。)

使用 AVX512,有_mm256_cvtepi64_epi32( vpmovqd)可以跨通道打包元素,并截断。


在 Ryzen 上,车道交叉洗牌很慢Agner Fog没有关于 的数字vpermd,但他列出了vpermps(可能在内部使用相同的硬件)3 uop、5c 延迟、每 4c 吞吐量一个。

vextractf128 xmm, ymm, 1在 Ryzen 上非常有效(1c 延迟,0.33c 吞吐量),这并不奇怪,因为它已经将 256b 寄存器作为两个 128b 的一半进行跟踪。 shufps也很有效(1c 延迟,0.5c 吞吐量),并且可以让您将两个 128b 寄存器混洗到您想要的结果中。

这也为vpermps您不再需要的 2 个随机掩码节省了 2 个寄存器。

所以我建议:

__m256d x = /* computed here */;

// Tuned for Ryzen.  Sub-optimal on Intel
__m128 hi = _mm_castpd_ps(_mm256_extractf128_pd(x, 1));
__m128 lo = _mm_castpd_ps(_mm256_castpd256_pd128(x));
__m128 odd  = _mm_shuffle_ps(lo, hi, _MM_SHUFFLE(3,1,3,1));
__m128 even = _mm_shuffle_ps(lo, hi, _MM_SHUFFLE(2,0,2,0));
Run Code Online (Sandbox Code Playgroud)

在 Intel 上,使用 3 次 shuffle 而不是 2 次可以获得 2/3 的最佳吞吐量,第一个结果的额外延迟为 1c。