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
可以有一个 m512
或 m32bcst
源。(指令通常有它们的内存广播粒度 = 它们的元素大小,不幸的是,在某些情况下,它根本没用。vpermilps
将控制向量作为内存操作数,而不是源数据。)
这比 vpermps zmm,zmm,zmm
稍微 好,因为随机播放有 1 个周期延迟而不是 3 个(在 Skylake-avx512 上)。
即使在循环之外,加载随机播放控制向量可能仍然是您的最佳选择。
我需要做以下两个操作:
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
可以有一个m512
或m32bcst
源。(指令通常有它们的内存广播粒度 = 它们的元素大小,不幸的是,在某些情况下,它根本没用。vpermilps
将控制向量作为内存操作数,而不是源数据。)
这比 vpermps zmm,zmm,zmm
稍微 好,因为随机播放有 1 个周期延迟而不是 3 个(在 Skylake-avx512 上)。
即使在循环之外,加载随机播放控制向量可能仍然是您的最佳选择。