Performance 128位/64位硬件无符号除法在某些情况下能否比x86-64 Intel/AMD CPU上的64位/32位除法更快?
是否可以通过硬件128位/64位除法指令执行缩放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
; 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)