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

时间:2010-12-05 23:29:17

标签: 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, %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操作符更好。

2 个答案:

答案 0 :(得分:21)

第二个问题:div是一个非常慢的指令(超过20个时钟周期)。上面的序列包含更多指令,但它们都相对较快,所以它在速度方面是一个净赢。

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

接下来的几条指令再次将结果乘以10,但避免使用mul / imul指令(这是否胜利取决于您所针对的确切处理器 - 较新的x86具有非常快的乘数,但是较旧的乘数不会。)

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

然后再次从i中扣除,以获得i - (i/10)*10 i % 10(对于无符号数字)。

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

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

最后评论:如果你想实际看到GCC计算模数,可以使用除数变量(例如函数参数),这样它就不能进行这种优化。无论如何,在x86上,您使用div计算模数。 div期望edx:eax中的64位被除数(edx中的高32位,eax中的低32位 - 如果使用32位数字,则清除edx为零)并将其除以您指定的任何操作数(例如div ebxedx:eax除以ebx)。它返回eax中的商和edx中的余数。 idiv对签名值执行相同的操作。

答案 1 :(得分:3)

第一部分,直到shrl $3, %edx,实现快速整数除法10.有一些不同的算法在预先知道划分的数字时有效。注意,858993459是“0.2 * 2 ^ 32”。这样做的原因是,即使指令集中存在整数除法指令div / idiv,它通常非常慢,比乘法慢几倍。

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