首页
社区
课程
招聘
[原创]被常数除的除法优化
发表于: 2010-9-17 13:53 5640

[原创]被常数除的除法优化

2010-9-17 13:53
5640
本来是个小技巧,看到 Dhuta 的<<汇编优化提示>>,俺就把以前验证的东西贴出来吧

调试时会经常看到形如

00401016    B8 67666666     mov     eax, 66666667
0040101B    F7E9            imul    ecx


的代码,此为编译器将除法代码优化为乘法运算的例子。

 

下面是个测试列子,都是 最大速度的优化。

例子很简单,输入一个整数,输出其除以2.5的结果。

 

例1:

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
 int i ;
 scanf("%d",&i) ;
 i = i/2.5 ;  //唯一不同的地方
 printf("i = %d\n",i) ;
 return 0;
}

 

00401000 >/$  51            push    ecx                              ;  msvcr90.__winitenv
00401001  |.  8D0424        lea     eax, dword ptr [esp]
00401004  |.  50            push    eax
00401005  |.  68 F4204000   push    test11.004020F4                  ; /format = "%d"
0040100A  |.  FF15 A4204000 call    dword ptr [<&MSVCR90.scanf>]     ; \scanf
00401010  |.  DB4424 08     fild    dword ptr [esp+8]        ;例1:将两个数作为浮点数相除
00401014  |.  DC35 00214000 fdiv    qword ptr [_real]
0040101A  |.  E8 11080000   call    test11._ftol2_sse
0040101F  |.  50            push    eax                              ; /<%d>
00401020  |.  68 F8204000   push    test11.004020F8                  ; |format = "i = %d",LF,""
00401025  |.  894424 10     mov     dword ptr [esp+10], eax          ; |
00401029  |.  FF15 9C204000 call    dword ptr [<&MSVCR90.printf>]    ; \printf
0040102F  |.  33C0          xor     eax, eax
00401031  |.  83C4 14       add     esp, 14
00401034  \.  C3            retn


例2:

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
 int i ;
 scanf("%d",&i) ;
 i = i*2/5 ;
 printf("i = %d\n",i) ;
 return 0;
}


 

00401000 >  51              push    ecx                              ; msvcr90.__winitenv
00401001    8D0424          lea     eax, dword ptr [esp]
00401004    50              push    eax
00401005    68 F4204000     push    test11.004020F4                  ; ASCII "%d"
0040100A    FF15 A4204000   call    dword ptr [<&MSVCR90.scanf>]     ; msvcr90.scanf
00401010    8B4C24 08       mov     ecx, dword ptr [esp+8]         ; /----------- 优化后的代码段,i = ecx = 输入值
00401014    03C9            add     ecx, ecx                         
00401016    B8 67666666     mov     eax, 66666667
0040101B    F7E9            imul    ecx
0040101D    D1FA            sar     edx, 1
0040101F    8BC2            mov     eax, edx
00401021    C1E8 1F         shr     eax, 1F
00401024    03C2            add     eax, edx                         ; \------------
00401026    50              push    eax
00401027    68 F8204000     push    test11.004020F8                  ; ASCII "i = %d",LF
0040102C    894424 10       mov     dword ptr [esp+10], eax
00401030    FF15 9C204000   call    dword ptr [<&MSVCR90.printf>]    ; msvcr90.printf
00401036    33C0            xor     eax, eax[LEFT][/LEFT]
00401038    83C4 14         add     esp, 14
0040103B    C3              retn


 

"66666667"这样的数字是事先计算好的值,不同的除法其值不同,这个例子中,编译器只会优化整除,所以编写代码时,需要手动优化,不能随意写成i/2.5 的格式。

 

"66666667"是怎么计算出来的?

对形如 x/y = z 的简单除法,有以下的转换:

x/y = z

x* (1/y) = z 

x* M = z

M = 1/y = 1^32 / ((1^32)*y) = (1^32/y) >> 32

此例中,y = 2/5 = 0.4 , 66666667 = 1^32 * 0.4 。

非常饶舌,1^32 怎么来的? 

估计还得从 "imul    ecx"这个指令说起,对于32位处理器,imul(乘法)将 eax 上的数字乘以 ecx 上的数,结果(64位值)的高32位送入寄存器 edx,低32位送入寄存器 eax。优化后的代码只取有用的数据 edx,eax的数据被舍弃,即故意的数据溢出(结果已超过32位),就好象 4*1.3 = 5 一样,结果的小数点后的数字被自动舍弃。

为了达到这种效果,必须构造一个足够大的数(1^32),使任何一个数与其相乘,其结果都能反应到 edx中(imul指令)。

再看 1/y = 1^32/((1^32)*y) =1^32/y * 1^(-32) = (1^32/y) >> 32

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
经典之作~
2010-9-18 00:04
0
游客
登录 | 注册 方可回帖
返回
//