Performance 128位/64位硬件无符号除法在某些情况下能否比x86-64 Intel/AMD CPU上的64位/32位除法更快?

Performance 128位/64位硬件无符号除法在某些情况下能否比x86-64 Intel/AMD CPU上的64位/32位除法更快?,performance,assembly,x86,x86-64,integer-division,Performance,Assembly,X86,X86 64,Integer Division,是否可以通过硬件128位/64位除法指令执行缩放64位/32位除法,例如: ; Entry arguments: Dividend in EAX, Divisor in EBX shl rax, 32 ;Scale up the Dividend by 2^32 xor rdx,rdx and rbx, 0xFFFFFFFF ;Clear any garbage that might have been in the upper half of RBX div rbx ; RAX = RD

是否可以通过硬件128位/64位除法指令执行缩放64位/32位除法,例如:

; Entry arguments: Dividend in EAX, Divisor in EBX
shl rax, 32  ;Scale up the Dividend by 2^32
xor rdx,rdx
and rbx, 0xFFFFFFFF  ;Clear any garbage that might have been in the upper half of RBX
div rbx  ; RAX = RDX:RAX / RBX
; Entry arguments: Dividend in EAX, Divisor in EBX
mov edx,eax  ;Scale up the Dividend by 2^32
xor eax,eax
div ebx  ; EAX = EDX:EAX / EBX
…在某些特殊情况下比硬件64位/32位除法指令执行的缩放64位/32位除法更快,例如:

; Entry arguments: Dividend in EAX, Divisor in EBX
shl rax, 32  ;Scale up the Dividend by 2^32
xor rdx,rdx
and rbx, 0xFFFFFFFF  ;Clear any garbage that might have been in the upper half of RBX
div rbx  ; RAX = RDX:RAX / RBX
; Entry arguments: Dividend in EAX, Divisor in EBX
mov edx,eax  ;Scale up the Dividend by 2^32
xor eax,eax
div ebx  ; EAX = EDX:EAX / EBX
我所说的“一些特殊情况”是指不寻常的红利和除数。 我只对比较
div
指令感兴趣

128位/64位硬件无符号除法在某些情况下能否比x86-64 Intel/AMD CPU上的64位/32位除法更快

理论上,任何事情都是可能的(例如,可能在50年后,Nvidia创建了一个80x86 CPU…)

然而,我想不出一个合理的理由来解释为什么128位/64位除法比x86-64上的64位/32位除法更快(不仅仅等同于)


我怀疑这是因为我假设C编译器的作者非常聪明,到目前为止,我没有让流行的C编译器在将一个无符号32位整数(左移32位)除以另一个32位整数时生成后一个代码。它总是编译成128位/64位div指令。另外,左移编译为精细到
shl


编译器开发人员很聪明,但编译器很复杂,而且C语言规则会妨碍他们。例如,如果您只执行a
a=b/c
(其中
b
为64位,
c
为32位)该语言的规则是
c
在除法发生之前被提升到64位,因此它最终成为某种中间语言中的64位除数,这使得后端翻译(从中间语言到汇编语言)变得困难告诉64位除数可以是32位除数。

您要问的是,当除数已知为32位时,如何将C除法优化为64b/32b=>32b x86 asm除法。当然,编译器必须避免在完全有效的(C)64位除法上出现
#DE
异常的可能性,否则它就不会遵循“仿佛”规则。所以它只能在可以证明商适合32位的情况下这样做

是的,这是一场胜利,或者至少是收支平衡。在某些CPU上,甚至值得在运行时检查这种可能性,因为64位除法要慢得多但不幸的是,当前的x86编译器没有优化器许可证来查找此优化,即使您设法为它们提供足够的信息,以证明它是安全的。e、 g.
if(edx>=ebx)\uuu内置的不可访问()没有帮助


对于相同的输入,32位操作数大小将始终至少保持相同的速度 16位或8位可能比32位慢,因为写入输出时可能存在错误的依赖关系,但写入32位寄存器零会扩展到64位以避免这种情况。(这就是为什么像哈罗德指出的那样,
mov ecx,ebx
是将ebx零扩展到64位的好方法,比不能编码为32位符号扩展立即数的值更好)。但除了部分寄存器的诡计,16位和8位除法通常也和32位一样快,甚至更差

在AMD CPU上,除法性能不取决于操作数大小,只取决于数据<具有128/64位的代码>0/1
应比任何较小操作数大小的最坏情况快。AMD的整数除法指令只有2个UOP(大概是因为它必须写入2个寄存器),所有逻辑都在执行单元中完成

Ryzen上的16位/8位=>8位除法是一个uop(因为它只需要写AH:AL=AX)


在英特尔CPU上,
div
/
idiv
被微编码为与多个UOP一样多的UOP
。对于最大32位(Skylake=10)的所有操作数大小,UOP的数量大致相同,但64位的速度要慢得多。(天湖
div r64
为36 uops,天湖
idiv r64
为57 uops)。参见Agner Fog的说明表:

在Skylake上,操作数大小高达32位的div/idiv吞吐量固定为每6个周期1个。但是
div/idiv r64
吞吐量是每24-90个周期一次

另请参见了解一个具体的性能实验,在该实验中,修改现有二进制文件中的REX.W前缀以将
div r64
更改为
div r32
会产生约3倍的吞吐量差异

并显示了在红利很小的情况下,为英特尔CPU进行调优时,使用32位除法的机会。但是你有一个大的红利和一个足够大的除数,这是一个更复杂的情况。这种叮当声优化仍然是将asm中红利的上半部分归零,从未使用非零或非符号扩展EDX


在将一个无符号32位整数(左移32位)除以另一个32位整数时,我无法让流行的C编译器生成后一个代码

我假设您首先将该32位整数强制转换为
uint64\u t
,以避免UB,并在C抽象机中获得一个正常的
uint64\u t/uint64\u t

这是有道理的:当商溢出AL/AX/EAX/RAX时,
edx>=ebx时,您的方式将不安全,它将导致
#DE
故障,而不是静默截断。没有办法禁用它

因此,编译器通常只在
cdq
cqo
之后使用
idiv
,而
div
仅在将高半部归零后使用,除非您使用内部或内联asm来暴露代码错误的可能性。在C语言中,
x/y
仅在
y=0
时出现故障(对于有符号,
INT\u MIN/-1
也允许出现故障1)

GNUC没有宽除法的固有特性,但MSVC有
\uUDIV64
。(带gcc/clang,d)