编译器整数除法的优化
整数的除法有2种情况
被除数是未知数
那么编译器会使用div指令,运行效率会下降
被除数/除数是常量
那么情况会比较复杂,如果除数是 2 n 2^n 2n,就会使用shr a,n。其中a是被除数,n是2的幂。如果除数不是2呢?那么情况就会更加复杂了,不同的编译器会有不同的算法。通常的思路是将除法转换成乘法来提高运行的效率,最常见的优化算法便是倒数相乘了
a b = a ∗ ( 1 b ) \frac{a}{b}=a*(\frac{1}{b}) ba=a∗(b1)
下面用一个实例说明
#include<stdio.h>
int main(){
int a;
scanf("%d",&a);
printf("%d",a/11);
return 0;
}
这是一个非常简单的整数除法例子,下面我列出核心汇编代码进行讲解
mov eax,2E8BA2E9
imul ecx //ecx是我们的a
sar edx,1
mov ecx,edx
shr ecx,1F
add edx,ecx
核心的除法优化算法其实就是短短的6行,那么这有什么可讲的呢?
首先eax是由编译器生成的一个数,然后 a ∗ e a x a*eax a∗eax,得到的积低32位存储在eax中,高32位存储在edx中,如果积大于32位的话。
sar edx,1
则是直接取出高32位来看,相当于直接舍去了低32位,也就相当于先右移了32位。再右移1位,相当于我们得到的积总共向右移了33位
a ∗ ( 2 E 8 B A 2 E 9 h > > 33 ) a*(2E8BA2E9h>>33) a∗(2E8BA2E9h>>33)等于 a ∗ ( 2 E 8 B A 2 E 9 h / 2 33 ) a*(2E8BA2E9h/2^{33}) a∗(2E8BA2E9h/233), ( 2 E 8 B A 2 E 9 h / 2 33 ) ≈ 0.090909 (2E8BA2E9h/2^{33})\approx0.090909 (2E8BA2E9h/233)≈0.090909也就是所谓的倒数了。
第5行的右移31位是为了得到结果的符号位,这里主要是看符号位是否为1,也就是是否为负数。如果是负数的话,符号位为1,最后的结果+1。
那么问题来了,为什么最后要加1呢?
这是因为负数算术右移跟负数除以2最后的结果其实是不同的,虽然我们常常认为右移1位就是除以2。
负数算术右移是向下取整的,换句话说 负 数 s h r 的 结 果 + 1 = 负 数 除 以 2 的 结 果 负数shr的结果+1 = 负数除以2的结果 负数shr的结果+1=负数除以2的结果。所以最后要加1。
当然这是有符号数的除法,如果是无符号数的除法,使用的汇编指令就是shr了。而无符号数永远为正,自然也不会存在这样的问题了。