AVX-512 中的 1 对 4 广播和 4 对 1 减少

1-to-4 broadcast and 4-to-1 reduce in AVX-512

我需要做以下两个操作:

float x[4];
float y[16];

// 1-to-4 broadcast
for ( int i = 0; i < 16; ++i )
    y[i] = x[i / 4];

// 4-to-1 reduce-add
for ( int i = 0; i < 16; ++i )
    x[i / 4] += y[i];

什么是高效的 AVX-512 实施?

对于reduce-add,只需像在Fastest way to do horizontal float vector sum on x86 to get a horizontal sum in each 128-bit lane, and then vpermps to shuffle the desired elements to the bottom. Or vcompressps 使用常量掩码来做同样的事情,可选地使用内存目标。

一旦打包成单个向量,您就有了一个普通的 SIMD 128 位加法。

如果你的数组实际上大于 16,而不是 vpermps,你可以 vpermt2ps 从两个源向量中的每一个中取出第 4 个元素,为你做 +=x[] 256 位向量的一部分。 (或者再次与另一个 shuffle 组合成 512 位向量,但这可能会成为 SKX 上 shuffle 吞吐量的瓶颈)。

在 SKX 上,vpermt2ps 只是一个 uop,具有 1c 的吞吐量/3c 的延迟,因此它非常高效,非常强大。在 KNL 上它有 2c 的吞吐量,比 vpermps 差,但也许仍然值得。 (KNL 没有 AVX512VL,但是如果你想用 256 位向量添加到 x[],你(或编译器)可以使用 AVX1 vaddps ymm。)

有关说明表,请参阅 https://agner.org/optimize/


对于负载:

这是在循环中完成的,还是重复进行的? (也就是说,你能在寄存器中保留一个洗牌控制向量吗?如果是这样,你可以

  • 使用 VBROADCASTF32X4 进行 128->512 广播(加载端口的单个 uop)。
  • 使用 vpermilps zmm,zmm,zmm 进行通道内洗牌,以在每个 128 位通道内广播不同的元素。 (必须与广播负载分开,因为内存源 vpermilps 可以有一个 m512m32bcst 源。(指令通常有它们的内存广播粒度 = 它们的元素大小,不幸的是,在某些情况下,它根本没用。vpermilps 将控制向量作为内存操作数,而不是源数据。)

这比 vpermps zmm,zmm,zmm 稍微 好,因为随机播放有 1 个周期延迟而不是 3 个(在 Skylake-avx512 上)。

即使在循环之外,加载随机播放控制向量可能仍然是您的最佳选择。