【问题标题】:Efficient (on Ryzen) way to extract the odd elements of a __m256 into a __m128?将 __m256 的奇数元素提取到 __m128 中的有效(在 Ryzen 上)方法?
【发布时间】:2018-02-02 15:47:55
【问题描述】:

是否有一种内在的或另一种有效的方法将 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

【问题讨论】:

  • 所以你想提取奇数或偶数的 32 位元素?即像 AVX512 _mm256_cvtepi64_epi32 (vpmovqd)?我认为您不会以 3 周期延迟击败 1 条 shuffle 指令,因为在 Intel CPU 上,车道交叉 shuffle 总是有 3c 延迟。您的vpermd 解决方案具有单周期吞吐量。
  • 如果您需要它更快,您将不得不减少周围的代码使用它,或者不需要车道交叉或其他东西!或者也许以某种方式使用shufps 将两个源打包成256b 结果(除非它不是车道交叉,所以它不能解决您的问题,并且没有vpackqd 指令和打包指令也不是车道交叉。)
  • @PeterCordes,是的,我想从 256 位寄存器中提取奇数或偶数 32 位元素到 128 位寄存器。感谢您对 AVX512 的参考!我在 Ryzen 1800X 上没有,但期待迁移一次……这些 32 位元素是 64 位双精度的高低部分,所以我没有看到改变周围代码的方法.
  • 那么它们必须在__m128i 中,还是您可以使用通道内随机播放将低半部分和高半部分放入__m256i 的每个通道的底部2 个元素中?不过,如果您正在为 Ryzen 进行调整,那么将其降低到 128b 可能确实有意义。但也许 vextractf128 然后使用 2-source shuffle(如 shufps)在 Ryzen 上会更好,因为在 Ryzen 上,跨车道的 shuffle 非常慢。

标签: c++ vectorization x86-64 sse avx2


【解决方案1】:

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

虽然如果针对 Intel 进行调整,您可能会尝试更改周围的代码,以便您可以洗牌到每个 128b 通道的底部 64 位,以避免使用通道交叉洗牌。 (那么你可以只使用vshufps ymm,或者如果调整KNL,vpermilps,因为2输入vshufps更慢。)

在 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 寄存器混洗成您想要的结果。

这还为您不再需要的 2 个 vpermps 随机掩码节省了 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));

在 Intel 上,使用 3 次随机播放而不是 2 次可以为您提供 2/3 的最佳吞吐量,第一个结果的延迟时间为 1c。

【讨论】:

  • 我测量到 const __m128i high32 = _mm256_castsi256_si128(_mm256_permutevar8x32_epi32(_mm256_castpd_si256(x), gHigh32Permute));const __m128i high32 = _mm_castps_si128( _mm256_castps256_ps128(_mm256_permutevar8x32_ps(_mm256_castpd_ps(x), gHigh32Permute) )); 快。那么也许doublefloat 绕过也会受到惩罚?
  • @SergeRogatch:不太可能用于洗牌。更有可能的是,vpermd 的表现与vpermps 不同。 (Agner 没有列出它们,所以我不得不猜测)。或者无论你使用什么结果,当它来自一个整数洗牌时会更好?不过,根据 Agner 的说法,AMD 对于实际的 FP 数学指令有浮点数和双数的差异。 (当然几乎总是无关紧要,但它是关于内部实现的线索,比如可能有一些额外的标签位与向量一起存储。)
  • 不应该将hilo 换成__m128 odd = _mm_shuffle_ps(hi, lo, _MM_SHUFFLE(3,1,3,1)); 吗?
  • @SergeRogatch:很好,是的,结果的低 2 个元素来自第一个源操作数。
  • @SergeRogatch:你说过一些关于令人困惑的文档......请参阅felixcloutier.com/x86/SHUFPS.html(或从中提取的原始英特尔 vol.2 PDF 以获取图表混乱的说明)。 “操作”部分对所有内容都有详细的伪代码,并且通常有很好的图表和表格。 (例如,对于 cmpps,请查看 cmppd,因为它是按字母顺序排列的,所以他们把好东西放在那里。)在线“内在查找器”很好,但有时会出错或遗漏一些重要细节。而且它从来没有图表。
猜你喜欢
  • 2012-06-22
  • 2012-10-22
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 2016-05-09
  • 2019-05-27
  • 2011-08-21
  • 2013-09-11
相关资源
最近更新 更多