苹果M1和英特尔AVX512上的基准划分和Libdivide

2021-05-13 03:41:43

Libdivide是Fish的库,用于通过运行时常量加快整数划分。它通过用更便宜的乘法和班次替换划分说明来工作。

Libdivide支持SIMD,最近对AVX-512和ARM NEON的支持。让我们在英特尔Xeon和Apple的M1上进行基准。

uint32_t sumq(const uint32_t * vals,size_t count,uint32_t d){uint32_t sum = 0; for(size_t i = 0; i<计数; i ++)sum + = vals [i] / d; //这个司是瓶颈退货总和; }

此函数的执行时间由鸿沟主导。它可以用libdivide优化:

uint32_t sumq(const uint32_t * vals,size_t count,uint32_t d){libdivide :: divider< uint32_t> div(d); uint32_t sum = 0; for(size_t i = 0; i<计数; i ++)sum + = vals [i] / div; //更快的Libdivide Divion Return Sum; }

这通常是2-10x的速度更快,取决于D和计数。它还解锁了矢量化,以便更加加速 - 示例。

以下时间用D = 7测量上述环路(“慢速案例”)和计数= 524K。循环重复30次,保持最佳(最快)结果。 1

与硬件相比,次数是每分的纳秒和百分比改进(即UDIV指令)。较低更好。

AVX512是AVX2宽度的两倍,但仅略微更快。首先,AVX512在一次串联,一个256位通道的过程中乘以一半的并行性。

Libdivide必须使用32x32-> 64 Muls(Mul_epu32)为矢量化64位,招致4x罚款2.即使在具有4倍并行性的AVX2中也希望打破。相反,AVX2比标量快,也许是因为32位乘以比64位更快。

M1比64位划分的Xeon快10倍。它是......只是哇。

0.624纳秒的硬件分割时间是3.2 GHz的干净倍数,表明每分的2个时钟周期的吞吐量。这是显着的:硬件分隔器通常是可变的延迟,并且难以向管道,因此每分的30+时钟周期是常见的。 M1每个核心有多个整数分部单位吗?

有趣地,标量64位比标量32位更快。这是因为AARCH64具有64位高乘数(UMULH)的专用指令,而32位高乘数以64位低乘法(Umull)执行,然后进行右移。

带有U32的霓虹灯允许只有两个Umull指令(Godbolt)的四个32位高倍增。所以霓虹灯U32应该是标量的几乎是速度的两倍;事实上,它甚至更快,可能是因为有态度的固定成本(分支等)。

与U64的霓虹灯较慢,因为它的乘数不够宽,因此它会引发4X罚款2,其瘦载体仅提供2倍并行性。 (尽管如此,它仍然可能是较大序列的一部分,以将数据保持在矢量寄存器中。)

即使使用M1的快速分频器,Libdivide仍然非常有效地加快整数划分。矢量化大大改善了32位,并且是64位的混合袋,但仍然比硬件更快。

为什么AVX512只略微比AVX2略快? umuls是每巷序列化,是它倒下吗?

Apple M1如何实现每个周期的.5划分的吞吐量?硬件分频器流水线,是否有超过每个核心?

使用Libdivide的基准工具收集结果。你可以自己运行:

git clone https://github.com/ridiculicfish/libdivide.gitmkdir libdivide / build&& CD Libdivide / BuildcMake ..&制作基准./Benchmark U32#或U64或S32或S64

7是libdivide的“慢速案例”,因为7的魔号必须是33或65位,因此不能符合寄存器。这和libdivide得到的缓慢。 ↩

要划分64位数,LibDivide需要64位产品的高半,所谓的高乘法。 不幸的是,SIMD硬件通常具有53位倍增器:对于双精度浮点,足够大,对Libdivide不够大。 因此,Libdivide必须将64位高乘以四个32x32-> 64乘以。 这会在SIMD中遭受64位矢量化分区的4个惩罚。 ↩↩2