为什么 gcc 或 ARM 上的 clang 不使用"Division by Invariant Integers using Multiplication"技巧?



有一个众所周知的技巧可以通过不变整数进行除法,实际上根本没有进行除法,而是进行乘法。在堆栈溢出以及使用乘法中的性能分区中讨论了这一点,而在实施整数划分时,海湾合作杂志为什么使用奇怪的数字使用乘法?

但是,我最近在AMD64和ARM上测试了以下代码(Raspberry Pi 3型B):

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char **argv)
{
  volatile uint64_t x = 123456789;
  volatile uint64_t y = 0;
  struct timeval tv1, tv2;
  int i;
  gettimeofday(&tv1, NULL);
  for (i = 0; i < 1000*1000*1000; i++)
  {
    y = (x + 999) / 1000;
  }
  gettimeofday(&tv2, NULL);
  printf("%g MPPSn", 1e3 / ( tv2.tv_sec - tv1.tv_sec +
                             (tv2.tv_usec - tv1.tv_usec) / 1e6));
  return 0;
}

该代码在手臂架构上的速度非常慢。相比之下,在AMD64上这非常快。我注意到在手臂上,它称为__aeabi_uldivmod,而在AMD64上,它实际上根本不分开,而是进行以下操作:

.L2:
        movq    (%rsp), %rdx
        addq    $999, %rdx
        shrq    $3, %rdx
        movq    %rdx, %rax
        mulq    %rsi
        shrq    $4, %rdx
        subl    $1, %ecx
        movq    %rdx, 8(%rsp)
        jne     .L2

问题是,为什么?手臂架构上是否有一些特定功能使这种优化不可行?还是仅仅是由于ARM架构的稀有性尚未实施吗?

在人们在评论中开始建议之前,我会说我尝试了GCC和Clang,还尝试了-O2和-O3优化级别。

在我的AMD64笔记本电脑上,它给出1181.35 MPP,而在Raspberry Pi上,它给出了5.50628 mpps。这超过2个数量级差异!

gcc仅使用乘法倒数来进行寄存器宽度或较窄的划分。您正在测试针对ARM32的X86-64,因此uint64_t在这种情况下给予X86-64 A 巨大优势。

在32位CPU上具有高通量倍数的32位CPU,例如Modern X86,以及您的Cortex-A7 ARM,如果它的乘数比其分层更好。p>只需使用32x32 => 64B作为构建块,将需要多个MUL指令才能获得64b x 64b => 128b的高度乘结果。(IIRC ARM32有此。)

但是,这是不是在任何优化级别上选择什么GCC或Clang。

如果您想残障X86 CPU,请使用-m32编译32位代码。X86 gcc -O3 -m32将使用__udivdi3。不过,我不会称之为"公平",因为64位CPU在64位算术时的速度要快得多,并且Cortex-A7没有64位模式。

otoh,在32位模式下,仅32位x86 cpu比当前的x86 cpu更快。在32位模式下未使用的额外晶体管的主要成本是模具区域和功率,而不是高端时钟速度。如果某些低功率预算CPU(例如ULV笔记本电脑芯片)可能在没有支持长度模式(x86-64)的情况下持续更长的时间(X86-64),但这很小。

因此,基准32位x86 vs. 32位的手臂可能会很有趣,只是要学习一些有关微体系结构的信息。但是,如果您关心64位整数性能,则一定要编译x86-64而不是x86-32。

最新更新