那个随机播放+强制转换_mm256_permutevar8x32_ps
对于一个向量来说是最优的在 Intel 和 Zen 2 或更高版本上。一条单微操作指令是您能得到的最好的指令。 (AMD Zen 2 和 Zen 3 上有两个微指令。Zen 4 上有一个微指令。https://uops.info/ https://uops.info/)
Use vpermps
代替vpermd
如果您的输入向量是由创建的,则可以避免 int / FP 绕过延迟的任何风险pd
指令而不是负载或其他东西。在 Intel 上,使用 FP 洗牌的结果作为整数指令的输入通常没问题(我不太确定是否将 FP 指令的结果提供给整数洗牌)。
如果针对 Intel 进行调整,您可以更改周围的代码,以便可以洗入每个 128 位通道的底部 64 位。它避免了交叉车道的混乱。 (然后你可以使用vshufps ymm
,或者如果针对 KNL 进行调整,vpermilps
由于 2 输入vshufps
速度较慢。)
有了 AVX512,就有它将元素跨通道打包,并进行截断。
Zen 1 上的车道交叉洗牌速度很慢. 阿格纳·雾 http://agner.org/optimize/没有号码vpermd
,但列出vpermps
(可能在内部使用相同的硬件)三个微指令,五个延迟周期,每四个吞吐量周期一个。https://uops.info/ https://uops.info/证实了 Zen 1 的这些数字。
Zen 2 和 Zen 3 大部分具有 256 位宽的向量执行单元,但有时它们的跨车道混洗与小于 128 位的元素需要多个微指令。 Zen 4 有所改进,例如 0.5 个周期的吞吐量vpermps
有四个周期的延迟。
vextractf128 xmm, ymm, 1
在 Zen 1 上非常高效(1c 延迟,0.33c 吞吐量),这并不奇怪,因为它将 256 位寄存器跟踪为两个 128 位一半。shufps
也很高效(1c 延迟,0.5c 吞吐量),并且可以让您将两个 128b 寄存器洗牌为您想要的结果。
这也为您节省了一个寄存器vpermps
洗牌面具,你不再需要了。 (一vpermps
获取您想要分组到高车道和低车道的元素vextractf128
。或者,如果延迟很重要,则两个控制向量为 2xvpermps
在单微指令的 CPU 上)因此对于多微指令的 CPUvpermps
,特别是 Zen 1,我建议:
__m256d x = /* computed here */;
// Tuned for Zen 1 through Zen 3. Probably sub-optimal everywhere else.
__m128 hi = _mm_castpd_ps(_mm256_extractf128_pd(x, 1)); // vextractf128
__m128 lo = _mm_castpd_ps(_mm256_castpd256_pd128(x)); // no instructions
__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));
在英特尔上,使用三次洗牌而不是两次洗牌可达到最佳吞吐量的三分之二,并且第一个结果有一个周期的额外延迟。
在 Zen 2 和 Zen 3 上,vpermps
是两个微操作数与一个微操作数vextractf128
, 提取 + 2xvshufps
优于 2xvpermps
.
Alder Lake 上的 E 核心也有两个 uopvpermps
但一微操作vextractf128
and vshufps xmm