C语言 AV512:合并水平求和和广播的最佳方式

jv4diomz  于 2023-06-21  发布在  其他
关注(0)|答案(1)|浏览(92)

已经有一个关于horizontal sums using AVX512的问题。我尝试做类似的事情,但在求和之后,我想将结果广播给__m512d变量中的所有8个元素。到目前为止,我已经尝试过:
1.使用英特尔提供的宏:

double sum = _mm512_reduce_add_pd( mvx );
sumx = _mm512_set1_pd( sum );

1.使用shuffle/permute,尽可能避免车道交叉:

sumx = mvx;

mvx = _mm512_shuffle_pd(mvx, mvx, 0b01010101);
sumx = _mm512_add_pd(mvx, sumx);

mvx = _mm512_permutex_pd(mvx, _MM_PERM_ABCD);
sumx = _mm512_add_pd(mvx, sumx);

mvx = _mm512_shuffle_pd(mvx, mvx, 0b01010101);
sumx = _mm512_add_pd(mvx, sumx);

mvx = _mm512_shuffle_f64x2(mvx,mvx, _MM_SHUFFLE(1,0,3,2));
sumx = _mm512_add_pd(mvx, sumx);

mvx = _mm512_shuffle_pd(mvx, mvx, 0b01010101);
sumx = _mm512_add_pd(mvx, sumx);

mvx = _mm512_permutex_pd(mvx, _MM_PERM_ABCD);
sumx = _mm512_add_pd(mvx, sumx);

mvx = _mm512_shuffle_pd(mvx, mvx, 0b01010101);
sumx = _mm512_add_pd(mvx, sumx);

1.使用@PeterCordes的提示,将add/shuffles减少到3:

sumx = mvx;

mvx = _mm512_shuffle_pd(mvx, mvx, 0b01010101);
sumx = _mm512_add_pd(mvx, sumx);

mvx = _mm512_permutex_pd(sumx, _MM_PERM_ABCD);
sumx = _mm512_add_pd(mvx, sumx);

mvx = _mm512_shuffle_f64x2(sumx,sumx, _MM_SHUFFLE(1,0,3,2));
sumx = _mm512_add_pd(mvx, sumx);

在每种情况下,mvx__m512d输入,sumx__m512d输出。
我正在使用英特尔编译器在英特尔Skylake CPU上进行基准测试:

  • 版本1:2.17s
  • 版本2:2.31s
  • 版本3:1.96s

这是我所能做的最好的,还是你看到了另一种方法来优化这个操作?

2w2cym1i

2w2cym1i1#

一般来说,最好的方法是 swap halves而不是narrowing,所以在两个halves中计算相同的总和。(特别是如果你不关心Zen 4或假设未来的CPU,在那里缩小到256位有吞吐量优势。)在__m512d中处理2^3 = 8个双精度运算只需要3个shuffle/add步骤,其中一个是通道内加法对。

**您的第二个版本正确地做到了这一点,并且在当前的CPU(Intel和Zen 4)上看起来是最佳的。

像你现在做的那样,先做低延迟的通道内 Shuffle 对乱序执行有好处,让更多的uop执行并提前几个周期退出,以便在调度程序和ROB中为新的可能独立的工作腾出空间。
在当前的英特尔CPU上,所有32位粒度或更宽粒度的512位跨通道 Shuffle 都具有相同的性能:1 uop用于端口5,具有3c延迟。通道内512位混洗对于端口5是1 uop,具有1c延迟。
在Zen 4上,vshufpdvpermilpd都具有相同的时序,vpermpd/vshuff64x2/vshuff32x4/valignq也是如此。(https://uops.info/)所有这些shuffle都有直接操作数用于其控制,因此编译器不必加载向量常量。

任何调整都只是基于猜测未来可能的CPU上可能更快的东西,比如未来支持AVX-512的英特尔E核,或者他们在未来的CPU中用作E核的精简版AMD Zen 4,如果他们改变执行单元而不仅仅是缓存的话。或者未来的大内核,可能有空间容纳多个512位 Shuffle 单元。这段代码在所有6个操作中都有串行依赖性,但是能够在更多端口上运行可能会让无序执行更好地同时运行这段代码和一些独立的周围代码,或者另一个逻辑核心。

使用最宽粒度的 Shuffle 历来是最好的。例如,vextractf64x4 ymm, zmm, imm在Zen 4上比vextractf64x2 xmm, zmm, imm更快,所以更喜欢前者来提取第三个128位块,即使你不介意它带来高垃圾。更少的大块意味着更少的可能排列、更短的多路复用链,因此可能具有更低的延迟或在更多的执行单元上运行。但是没有vshuff64x4,只有vshuff64x2 128位块,所以这是我们交换256位半块的唯一好选择。
如果专门针对Zen 4进行调优而不考虑Intel,vextractf64x4 + vinsertf64x4的总延迟比vshuff64x2 zmm低,尽管它的前端成本是2个uops而不是1个uops。除了插入/提取256位的一半,Zen 4上的512位shuffle占用了它们的执行单元2个周期(1/时钟的实际吞吐量是两个端口中任何一个的一个uop的一半,就像Zen 4处理其他512位uop的方式一样,这些uop不需要在两个半之间移动数据)。

对于中间混洗,交换128位的一半,选择是vshuff64x2 z,z,z,imm8vpermpd z,z,imm8。两者在当前的CPU上运行相同,包括Zen 4。我们可能会根据更宽的粒度(以128位块而不是64位块移动数据)选择vshuff64x2,但还有另一个因素需要考虑:vpermpd z,z,imm8在每一半中进行独立的256位混洗,因此对于256位执行单元进行平凡的分解。(与向量控制版本不同,该版本有八个3位索引可从整个向量中选取。

Zen 4的shuffle执行单元本质上是512位宽的,所以它们只会为512位操作带来一些吞吐量和更高的延迟。但未来可能的英特尔E核AVX-512可能不会这样做,并可能运行vshuff64x2 z,z,z,imm8慢如Zen 1运行vperm2f128 y,y,y,imm8(8 uops,虽然这似乎过多)或vpermps y,y,y(3 uops)。桤木Lake中的英特尔E核可以将这些处理为2个uop,因此支持AVX-512的256位执行单元的E核也可以将vshuff64x2 z,z,z,imm8处理为2个uop。
vshuff64x2 z,z,z,imm8采用1024位的输入(因为它可以采用两个不同的输入向量),但是从第一输入选择前两个输出通道(因此只有4个可能的输入位必须通过多路复用器路由到每个输出位),并且对于来自第二源的后两个输出通道也是如此。因此,它可以分解为两个单独的512位输入/ 256位输出shuffle,如256位valignq ymm,ymm,ymm, immvperm2f128 ymm, ymm, ymm, imm,但每个输出通道都能够选择四个中的任何一个。(valignq zmm实际上是最终 Shuffle 的另一种可能性,但不太可能便宜。
因此,vshuff64x2 zmm实际上是以一种方式设计的,它可能比您想象的更便宜,比valignqvpermt2ps或其他2输入shuffle更容易实现,其中每个输出可以从两个512位输入的任何地方选择。

有人可能会猜测,一个单输入shuffle_mm512_permute_pd(mvx, 0b01'01'01'01);(又名vpermilpd z,z, imm在未来的CPU上可能比你的vshufpd z,z,z, imm使用相同的输入两次更有效。这在Knight's Landing(Xeon Phi)上实际上是正确的,但我假设您不关心这一点,因为它已经停产了几年,并且我没有查看vpermpdvpermpd的时间。vshuff64x2在上面。
但在Ice Lake上,更常见的vshufpd y,y,y,i的吞吐量为2/时钟,而1/时钟vpermilpd y,y,i 1.因此,谁能猜到在未来的AVX-512 E核或未来的大核上哪种 Shuffle 会更快,因为大核可能有多个512位 Shuffle 单元的空间。

总结:

  • vshufpd可以用于第一次 Shuffle 。即使向量在内存中启动,您也不需要内存源vpermilpd,因为您需要向量的另一个副本作为vaddpd的输入。可以去任何一种方式对未来的电子核心处理一个或另一个更便宜。这是一个在车道 Shuffle ,所以它分解为多个较窄的 Shuffle 平凡的E核心。
  • vpermpd-immediate对于中间的shuffle(交换128位对)是一个很好的选择;未来的E核很可能可以有效地处理它(作为两个独立的256位半)。vshuff64x2可以分解为两个独立的512位输入/ 256位输出shuffle,所以它也不错。

带有向量控制操作数的vpermpd不容易分解,但它是一个不同的操作码,所以希望即时控制版本仍然很便宜,即使向量控制版本更慢。不知何故,桤木Lake E-cores确实设法将vpermps ymm作为2个uops运行。

  • vshuff64x2valignq同样适合在Intel CPU上交换256位的一半,并且在Zen 4上彼此相等。vshuff64x2显然更易于E核高效实现:两者具有相同的输入量(1024位),但是vshuff64x2对于任何给定的输出位具有明显更少的可能源(4对. 16,并且如果两个源不是相同的寄存器,则对哪个源馈送哪个输出有更多限制)。此外,它可能是一个更常用的 Shuffle ,所以架构师更有可能花费晶体管,使它不太慢。

vextractf64x4 + vinsertf64x4在Zen 4上的延迟较低,这可能会或可能不会影响,具体取决于周围的代码。但是vshuff64x2 zmm在Zen 4上仍然是单uop,只有4个周期的延迟,就像其他512位的跨线 Shuffle 一样。假设使用AVX-512的较小内核可能会将其作为2个或更多个内核运行。

脚注1:IDK为什么Ice Lake /桤木Lake不能用寄存器源和立即控制将vpermilpd解码为读取相同输入两次的vshufpd uop,因为在这种情况下,相同的立即位将产生相同的 Shuffle 。看起来像是一个错过的优化,尽管可能在解码器中的某个地方会产生一个成本,用于为内存源版本与2个输入用于寄存器源版本。因此,在这种情况下,将shuffle执行单元改为复制一个输入,作为让端口1处理vpermilpd uops的一种方式,使其不特殊地处理内存源。代价是必须处理混洗单元的端口1输入上的更多不同的控制输入?

在Ice Lake /桤木Lake上,当没有512位uop正在运行时,可以处理部分但不是全部128位和256位shuffle的端口1执行单元可能只是通常可以从端口5访问的512位shuffle执行单元的一半。(与它们在端口0或1上处理256位FP数学指令的方式相同,但当端口1关闭时,它作为单个512位FMA单元工作。)因此,当shuffle单元是端口5上的vpermilpd zmm, zmm, imm8的上半部分时,它的通道可以处理vpermilpd,因此当通过端口1访问时,它似乎只需要最少的额外逻辑就可以做到这一点。(vpermilpd zmmvshufpd zmm使用其立即数的高4位的方式彼此相同,低4位也适用于低半部分。每个128位通 prop 有2位控制输入。)
我想知道是否有意确保vpermilpd/ps不能从FP数学运算(256位端口0和1)中窃取周期。这可能是有意义的,甚至可能有助于人们调优p01吞吐量与混洗吞吐量:他们可以使用vshufpd y, same,same, i让它在端口1或5上运行,或者只用于较小的机器码大小(2字节VEX)。或者vpermilpd y, ymm/mem, i将其限制在端口5,如果vshufpd不需要3字节VEX,则会增加一个额外的机器码字节。(或者如果它正在混洗存储器源,则是整个单独的指令。但是,与许多具有立即操作数的指令一样,Intel CPU不能微融合load+ALU uop,因此发出带宽的成本是相同的。

这似乎不太可能。也许他们只是分析了现有的代码,发现shufpd/vshufpd更常见,因此更重要;这并不奇怪,因为shufpd是SSE 2,但vpermilpd直到AVX 1才存在。因此,这个因素可能是影响这个设计的原因,这与选择YMM Shuffle 有关,即使vshufpd ymmvpermilpd都是AVX 1的新产品。
但猜测未来,英特尔gracemont E核心在桤木湖有相同的性能为vpermilpd ymm, ymm, i8与。vshufpd ymm, ymm, ymm, i8 .

相关问题