模(%)的GCC实现是如何工作的,为什么不使用div指令?

模(%)的GCC实现是如何工作的,为什么不使用div指令?,gcc,assembly,optimization,x86,Gcc,Assembly,Optimization,X86,我试图解决如何在汇编中计算模10的问题,所以我在gcc中编译了下面的c代码,以了解它的结果 unsigned int i=999; unsigned int j=i%10; 令我惊讶的是,我得到了 movl -4(%ebp), %ecx movl $-858993459, %edx movl %ecx, %eax mull %edx shrl $3, %edx movl %edx, %eax sall $2, %eax addl %edx, %e

我试图解决如何在汇编中计算模10的问题,所以我在gcc中编译了下面的c代码,以了解它的结果

unsigned int i=999;
unsigned int j=i%10;
令我惊讶的是,我得到了

movl    -4(%ebp), %ecx
movl    $-858993459, %edx
movl    %ecx, %eax
mull    %edx
shrl    $3, %edx
movl    %edx, %eax
sall    $2, %eax
addl    %edx, %eax
addl    %eax, %eax
movl    %ecx, %edx
subl    %eax, %edx
movl    %edx, %eax
movl    %eax, -12(%ebp)
其中-4(%ebp)或“i”是输入,而-12(%ebp)或“j”是答案。我已经测试过了,不管你做的是什么数字-4(%ebp),它都能工作


我的问题是这段代码是如何工作的,以及它如何比使用div操作数更好。

第一部分,最高为
shrl$3,%edx
,实现了快速整数除以10。有几种不同的算法,当你除以的数字是预先知道的时候,它们会起作用。请注意,858993459是“0.2*2^32”。这样做的原因是,即使指令集中存在整数除法指令
div
/
idiv
,它通常非常慢,比乘法慢几倍


第二部分通过将除法的结果乘以10(通过移位和加法间接计算;可能编译器认为这样会更快)然后从原始数字中减去余数来计算余数。

第二个问题第一:
div
是一条非常慢的指令(超过20个时钟周期)。上面的序列包含更多指令,但它们都相对较快,因此就速度而言,这是一个净胜利

前五条指令(包括
shrl
)计算i/10(我将在一分钟内解释如何计算)

接下来的几条指令将结果再次乘以10,但避免使用
mul
/
imul
指令(这是否成功取决于您的目标处理器-较新的x86具有非常快的乘法器,但较旧的x86没有)

然后再次从
i
中减去该值,得到
i-(i/10)*10
,即
i%10
(对于无符号数字)

最后,关于i/10的计算:基本思想是用1/10的乘法代替10的除法。编译器通过乘以(2**35/10+1)对其进行定点近似-这是加载到
edx
中的魔法值,尽管它输出为有符号值,即使它实际上是无符号的-并将结果右移35。结果证明,这为所有32位整数提供了正确的结果

有一些算法可以确定这种近似值,保证误差小于1(对于整数,这意味着它是正确的值),而GCC显然使用1:)


最后一句话:如果你真的想看到GCC计算一个模,那么就使用除数变量(例如一个函数参数),这样它就不能进行这种优化。无论如何,在x86上,可以使用
div
计算模
div
期望在
edx:eax
中得到64位红利(edx中的高32位,eax中的低32位-如果使用32位数字,则将edx清除为零),并将其除以指定的任何操作数(例如,
div ebx
除以
edx:eax
)。它在
eax
中返回商,在
edx
中返回余数<代码> iDIV<代码>对签名值也一样。

你熟悉32位吗?@ Jasbj.O.rnHaGer--我会考虑到很多改进的结果和扩展的文件:
movl    %edx, %eax   ; eax=i/10
sall    $2, %eax     ; eax=(i/10)*4
addl    %edx, %eax   ; eax=(i/10)*4 + (i/10) = (i/10)*5
addl    %eax, %eax   ; eax=(i/10)*5*2 = (i/10)*10