微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

我有一个包含 A、B、C、D 4 个浮点数的浮点数组,我希望将它们加载到一个 Selector 变量中,例如 AABBCCDD。做到这一点的最佳方法是什么? 我知道使用 __m256 始终是一种选择,但使用 8 个 cpu 指令似乎很慢。谢谢。

解决方法

如果您的数据是另一个向量计算的结果(在 __m128 中),您需要控制向量为 vpermps 的 AVX2 _mm256_permutexvar_ps (_mm256_set_epi32(3,3,2,1,0))。>

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

如果它是单独标量计算的结果,您可能需要将它们与 _mm_set_ps(d,d,c,c) (1x vshufps) 一起混洗以设置 vinsertf128。


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

在 Intel 和 Zen2(256 位 shuffle 执行单元)上,使用 in-lane shuffle 的延迟低于跨道。这仍然需要一个 32 字节的 shuffle 控制向量常量,但如果您需要经常这样做,它通常/希望在缓存中保持热状态。

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

现代 CPU 直接在加载端口中处理广播加载,不需要 shuffle uop。 (Sandybridge 确实需要用于 vbroadcastf128 的端口 5 shuffle uop,与更窄的广播不同,但 Haswell 及更高版本纯粹是端口 2/3。但 SnB 不支持 AVX2,因此粒度小于 128 的车道交叉 shuffle-位不是一个选项。)

所以即使 AVX2 可用,我认为 AVX1 指令在这里更有效。在 Zen1 上,vbroadcastf128 为 2 uop,而 128 位 vmovups 为 1,但 vpermps(车道交叉)为 3 uop,而 vpermilps 为 2。

不幸的是,clang 将其简化为 vmovups 加载和 vpermps ymm,但 GCC 将其编译为书面形式。 (Godbolt)


如果您想避免使用随机播放控制向量常量,vpmovzxdq ymm,[mem](英特尔上为 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[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,250
        vinsertf128     ymm0,ymm0,0x1
        ret

所以 3 shuffle uops,不太好。它本可以使用 vshufps 而不是 vpermilps 来节省代码大小并让它在 Ice Lake 上的更多端口上运行。但仍然比 8 条指令要好得多。

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

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

_mm_load_ps -> _mm256_castps128_ps256 -> _mm256_permute_ps

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。