编译器除法优化

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译器除法优化相关的知识,希望对你有一定的参考价值。

我们知道除法在现代CPU计算中耗费更多的时钟周期:
如下图的Adddiv做的对比


Div延迟60-80之间 而add只有 1,可见我们在真实的情况应当避免调用除法汇编指令。

解决办法:

对于除以2的倍数优化

对于要进行除法运行的且除数是2的倍数,我们可以通过位移来完成
如下图:

int x = 58;
// x = x%8
x >>= 3;

但是对于负数来说位移是可能发生错误现象的如下:

对于负数我们可以利用如下公式进行转化

举个例子:
a = -58
b = 8

我们本例中结合计算机特性符合如下公式(比如结果是-7.25我们应该向上取而不是向下):

ceil ((a+b-1)/b) =ceil ((-58+8-1)/8) =ceil(-6.375)=-7

于是我们的除法优化可以如此这般消除除法

	int x = -58;
	//如果被除数小于0 利用公式消除除法
	if (x < 0)
	
		//x = (x + 8 - 1) / 8; => (x + 7) >> 3;
		//计算机默认向下取整
		x = (x + 7) >> 3;
	
	else 
		x >>= 3;
	

我们看看如下代码:

#include<stdio.h>

int main(int argc,char* args[])

	
	printf("%d\\r\\n",argc/8);

	return 0;


编译后会变为如下汇编指令


mov eax,dword ptr[argc] 移动到寄存器
cdq 把eax扩展为64位高位移动到edx上.此处是为了处理正负数的情况
and edx ,7 如果为正数 edx比为0,如果为负数edx全为1。因此正数执行后 edx 为0,负数为7。
add eax,edx 想当与我们上面的(x + 7),不过后面的7可能为0
sar eax,3 eax算数右移动3位保证

除以非2倍数优化

我们可以利用以下公式

反推公式如下:

tip: M 不会被整除 因为2^n 是偶数 而c是非2的倍数,所以结果默认向下取整,这个结论在负数情况需要注意

其中 M是常量所以可以被编译器优化为编译常量,其中n至少为32n越大结果越精确)

举例如下:

无符号除法

int main(unsigned int argc, char* args[])
	
	//注意无符号除法结果为无符号、、argc是无符号数
	printf("y  ===>>> %d\\r\\n", argc / 3);
	return 0;

对应的汇编语句:

其中0AAAAAAABh就是的我们公式中M

; eax = M
mov eax,0AAAAAAABh
; 其中edx是存储高32位 eax是低32位 ret标记为我们的计算结果。argc为a  于是乎得到
; (edx,eax) = ret = eax * argc   = aM  
mul eax,dword ptr [argc]
;edx由于是高32,移动一位相当于ret移动33位,也就是 ret>>33  等价  edx=edx/(2^33)
shr edx,1 

我们借用公式反推得到除数

可以看到上面整个算式中没有一个除法指令。
例外强悍的IDA pro可以帮我们快速识别

有符号除法

#include<stdio.h>
int main( int argc, char* args[])

	//注意argc是有符号的
	printf("y  ===>>> %d\\r\\n", argc / 3);
	return 0;


相较于无符号的除法多出以下汇编


0040104B  mov         eax,edx  
;1F转为10进制31
0040104D  shr         eax,1Fh  
00401050  add         eax,edx 

之所以多出几行行汇编是用于规避负数除法默认向下取整问题,所以为负数的时候需要加1.(参考上文)

;将a*m乘积的高位给eax
0040104B  mov         eax,edx  
;1F转为10进制31
;右移动31后只剩下符号位也就是1或者0
;负数为1 正数为0
0040104D  shr         eax,1Fh  
;如果是负数eax为1 那么进行加一补位
00401050  add         eax,edx 

M大于32位数的变形

#include<stdio.h>

int main(unsigned int argc, char* args[])

	//注意argc是有符号的
	printf("y  ===>>> %d\\r\\n", argc / 7);	
	return 0;


上面的汇编转化为数学公式如下:

其中M24924925h,而我们原来真正公式M2^32+M 很明显这数32寄存器不足以存放,以及除以2^35也是同理

以上是关于编译器除法优化的主要内容,如果未能解决你的问题,请参考以下文章

编译器除法优化

逆向-除法优化下

逆向课程第五讲逆向中的优化方式,除法原理,以及除法优化下

编译器求余优化

编译器求余优化

PC逆向之代码还原技术,第六讲汇编中除法代码还原以及原理第二讲,被除数是正数 除数非2的幂