用最少的指令加载并复制 4 个单精度浮点数到打包的 __m256 变量中

问题描述 投票:0回答:2

我有一个包含 A、B、C、D 4 个浮点数字的浮点数组,我希望将它们加载到像 ABBCCDD 这样的

__m256
变量中。最好的方法是什么? 我知道使用
_mm256_set_ps()
始终是一种选择,但对于 8 个 CPU 指令来说它似乎很慢。谢谢。

c++ avx
2个回答
6
投票

如果您的数据是另一个向量计算的结果(并且在 __m128 中),您需要 AVX2

vpermps
(
_mm256_permutevar8x32_ps
) 以及控制向量
_mm256_set_epi32(3,3, 2,2,  1,1, 0,0)

vpermps ymm
在 Intel 上为 1 uop,但在 Zen2 上为 2 uop(具有 2 个周期吞吐量)。 Zen1 上有 3 个 uop,每 4 个时钟吞吐量 1 个。 (https://uops.info/)

如果它是单独标量计算的结果,您可能需要使用

_mm_set_ps(d,d, c,c)
(1x vshufps)将它们混在一起以设置 vinsertf128。


但是对于内存中的数据,我认为最好的选择是128位广播负载,然后是车道内随机播放。 它只需要 AVX1,在现代 CPU 上,它在 Zen2 和 Haswell 及更高版本上是 1 个负载 + 1 个随机 uop。 它在 Zen1 上也很高效:唯一的跨车道随机播放是 128 位广播负载。

在 Intel 和 Zen2(256 位 shuffle 执行单元)上,使用通道内 shuffle 的延迟低于通道交叉。 这仍然需要 32 字节的随机播放控制向量常量,但如果您需要频繁执行此操作,它通常/希望在缓存中保持热状态。

__m256  duplicate4floats(void *p) {
   __m256 v = _mm256_broadcast_ps((const __m128 *) p);   // vbroadcastf128
   v = _mm256_permutevar_ps(v, _mm256_set_epi32(3,3, 2,2,  1,1, 0,0));  // vpermilps
   return v;
}

现代 CPU 直接在加载端口处理广播负载,无需 shuffle uop。 (Sandybridge 确实需要端口 5 shuffle uop 来实现

vbroadcastf128
,与较窄的广播不同,但 Haswell 及更高版本纯粹是端口 2/3。但 SnB 不支持 AVX2,因此粒度小于 128 位的跨车道 shuffle 是不可行的没有选择。)

所以即使 AVX2 可用,我认为 AVX1 指令在这里更有效。 在 Zen1 上,

vbroadcastf128
为 2 uops,而 128 位
vmovups
为 1 uops,但
vpermps
(车道交叉)为 3 uops,而
vpermilps
为 2 uops。

不幸的是,clang 将其悲观化为

vmovups
加载和
vpermps ymm
,但 GCC 按编写方式编译它。 (神箭)


如果您想避免使用洗牌控制向量常量,

vpmovzxdq ymm, [mem]
(Intel 上为 2 uops)可以获取为
vmovsldup
(1 uops 通道内洗牌)设置的元素。 或者广播加载并
vunpckl/hps
然后混合?


我知道使用 _mm256_set_ps() 始终是一个选项,但对于 8 个 CPU 指令来说它似乎很慢。

那就找一个更好的编译器吧! (或者记得启用优化。)

__m256  duplicate4floats_naive(const float *p) {
   return _mm256_set_ps(p[3],p[3], p[2], p[2], p[1],p[1], p[0],p[0]);
}

用 gcc (https://godbolt.org/z/dMzh3fezE) 编译成

duplicate4floats_naive(float const*):
        vmovups xmm1, XMMWORD PTR [rdi]
        vpermilps       xmm0, xmm1, 80
        vpermilps       xmm1, xmm1, 250
        vinsertf128     ymm0, ymm0, xmm1, 0x1
        ret

所以 3 个 shuffle uops,不太好。 它可以使用

vshufps
而不是
vpermilps
来节省代码大小并让它在 Ice Lake 上的更多端口上运行。 但仍然比 8 条指令好得多。

clang 的 shuffle 优化器与我优化的内在函数生成相同的汇编,因为 clang 就是这样的。 这是相当不错的优化,只是不太理想。

duplicate4floats_naive(float const*):
        vmovups xmm0, xmmword ptr [rdi]
        vmovaps ymm1, ymmword ptr [rip + .LCPI1_0] # ymm1 = [0,0,1,1,2,2,3,3]
        vpermps ymm0, ymm1, ymm0
        ret

1
投票

_mm_load_ps -> _mm256_castps128_ps256 -> _mm256_permute_ps

© www.soinside.com 2019 - 2024. All rights reserved.