分享
 
 
 

32位代码优化常识

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

32位代码优化常识

前言: WIn32 ASM的代码优化常识,非常有帮助。:)

--- Crossbow 整理

关于代码优化的文章实在太多了,遗憾的是大部分我都没有看,尽管他们就摆在我的床边(每当我要看的时候就忍不住打哈欠...嘿嘿)...

代码优化的含义:

代码优化的目标当然是体积小和速度快,但是在通常的情况下二者就象鱼和熊掌一样不能得兼,我们通常寻找的是这二者的折中,究竟应该偏向何方,那就得具体看我们的实际需要.

但有些常识是我们应该牢记的,下面就结合我们最常遇到的具体情况来漫谈一下:

1.寄存器清0

我绝对不想再看到下面的写法:

1) mov eax, 00000000h ;5 bytes

看起来上面的写法很符合逻辑,但你应当意识到还有更加优化的写法:

2) sub eax, eax ;2 bytes

3) xor eax, eax ;2 bytes

看看后面的字节数你就应该理解为什么要这么作了,除此之外,在速度上也没有损失,他们一样快,但你喜欢xor还是sub呢?我是比较喜欢xor,原因很简单,因为我数学不好....

不过Microsoft比较喜欢sub....我们知道windows运行的慢....(呵呵,当然是玩笑这并不是真正原因X-D!)

2.测试寄存器是否为0

我也不希望看到下面的代码:

1) cmp eax, 00000000h ;5 bytes

je _label_ ;2/6 bytes (short/near)

[* 注意很多指令针对eax作了优化,你要尽可能多地实用eax,比如CMP EAX, 12345678h (5 bytes)

如果你使用其他寄存器,就是6bytes *]

让我们看看,简单的比较指令居然要用7/11 bytes,No No No,试试下面的写法:

2) or eax, eax ;2 bytes

je _label_ ;2/6 (short/near)

3) test eax, eax ;2 bytes

je _label_ ;2/6 (short/near)

呵呵,只有4/8 bytes,看看我们可节省多少字节啊3/4字节...那么接下来的问题是你喜欢OR还是TEST呢,就我个人而言,比较喜欢TEST,因为test不改变任何寄存器,并不向任何寄存器写入内容,这通常能在pentium机上取得更快的执行速度.

别高兴的太早,因为还有更值得我们高兴的事情,假如你要判断的的是eax寄存器,那么看看下面的,是不是更有启发?

4) xchg eax, ecx ;1 byte

jecxz _label_ ;2 bytes

在短跳转的情况下我们比2)和3)又节省了1字节.oh....___...

3.测试寄存器是否为0FFFFFFFFh

一些API返回-1,因此如何测试这个值呢?看你可能又要这样:

1) cmp eax, 0ffffffffh ;5 bytes

je _label_ ;2/6 bytes

hey,不要这样,写代码的时候想一想,于是有了下面的写法:

2) inc eax ;1 byte

je _label_ ;2/6 bytes

dec eax ;1 byte

可以节省3 bytes并且执行速度会更快.

4.置寄存器为0FFFFFFFFh

看看假如你是Api的作者,如何返回-1?这样吗?

1) mov eax, 0ffffffffh ;5 bytes

看了上面的不会再这么XXX了吧?看看下面的:

2) xor eax, eax / sub eax, eax ;2 bytes

dec eax ;1 byte

节省一个字!还有写法:

3) stc ;1 byte

sbb eax, eax ;2 bytes

这有时还可以优化掉1 byte:

jnc _label_

sbb eax, eax ;2 bytes only!

_label_: ...

我们为什么用asm呢?这就是原因.

5.寄存器清0并移入低字数值

1) xor eax, eax ;2 bytes

mov ax, word ptr [esi+xx] ;4 bytes

????--->不会吧,这可能是最多初学者的写法了,我当然原来也是,看了benny的文章之后我决定改写为:

2) movzx eax, word ptr [esi+xx] ;4 bytes

收获2 bytes!

下面的

3) xor eax, eax ;2 bytes

mov al, byte ptr [esi+xx] ;3 bytes

就相应改为:

4) movzx eax, byte ptr [esi+xx] ;4 bytes

我们应当尽可能利用movzx

5) xor eax, eax ;2 bytes

mov ax, bx ;3 bytes

因为执行速度不慢并通常能节省字节...

6) movzx eax, bx ;3 bytes

6.关于push,下面是着重代码体积的优化,因为寄存器操作总要比内存操作要快.

1) mov eax, 50h ;5 bytes

这样就小了1 word

2) push 50h ;2 bytes

pop eax ;1 byte

当操作数只有1字节时候,push只有2 bytes,否则就是5 bytes,记住!

下一个问题,向堆栈中压入7个0

3) push 0 ;2 bytes

push 0 ;2 bytes

push 0 ;2 bytes

push 0 ;2 bytes

push 0 ;2 bytes

push 0 ;2 bytes

push 0 ;2 bytes

占用14字节,显然不能满意,优化一下

4) xor eax, eax ;2 bytes

push eax ;1 byte

push eax ;1 byte

push eax ;1 byte

push eax ;1 byte

push eax ;1 byte

push eax ;1 byte

push eax ;1 byte

可以更紧凑,但会慢一点的形式如下:

5) push 7 ;2 bytes

pop ecx ;1 byte

_label_: push 0 ;2 bytes

loop _label_ ;2 bytes

可以节省7字节....

有时候你可能会从将一个值从一个内存地址转移到另外内存地址,并且要保存所有寄存器:

6) push eax ;1 byte

mov eax, [ebp + xxxx] ;6 bytes

mov [ebp + xxxx], eax ;6 bytes

pop eax ;1 byte

试试push,pop

7) push dword ptr [ebp + xxxx] ;6 bytes

pop dword ptr [ebp + xxxx] ;6 bytes

7.乘法

当eax已经放入被乘数,要乘28h,如何来写?

1) mov ecx, 28h ;5 bytes

mul ecx ;2 bytes

好一点的写法如下:

2) push 28h ;2 bytes

pop ecx ;1 byte

mul ecx ;2 bytes

哇这个更好::

3) imul eax, eax, 28h ;3 bytes

intel在新CPU中提供新的指令并不是摆设,需要你的使用.

8.字符串操作

你如何从内存取得一个字节呢?

速度快的方案:

1) mov al/ax/eax, [esi] ;2/3/2 bytes

inc esi ;1 byte

代码小的方案:

2) lodsb/w/d ;1 byte

我比较喜欢lod因为他小,虽然速度慢了点.

如何到达字符串尾呢?

JQwerty's method:

9) lea esi, [ebp + asciiz] ;6 bytes

s_check: lodsb ;1 byte

test al, al ;2 bytes

jne s_check ;2 bytes

Super's method:

10) lea edi, [ebp + asciiz] ;6 bytes

xor al, al ;2 bytes

s_check: scasb ;1 byte

jne s_check ;2 byte

选择哪一个?Super的在386以下的更快,JQwerty的在486以及pentium上更快,体积一样,选择由你.

9.复杂一点的...

假设你有一个DWORD表,ebx指向表的开始,ecx是指针,你想给每个doword加1,看看如何作:

1) pushad ;1 byte

imul ecx, ecx, 4 ;3 bytes

add ebx, ecx ;2 bytes

inc dword ptr [ebx] ;2 bytes

popad ;1 byte

可以优化一点,但是好像没人用:

2) inc dword ptr [ebx+4*ecx] ;3 bytes

一条指令就节省6字节,而且速度更快,更易读,但好像没有什么人用?...why?

还可以有立即数:

3) pushad ;1 byte

imul ecx, ecx, 4 ;3 bytes

add ebx, ecx ;2 bytes

add ebx, 1000h ;6 bytes

inc dwor ptr [ebx] ;2 bytes

popad ;1 byte

优化为:

4) inc dword ptr [ebx+4*ecx+1000h] ;7 bytes

节省了8字节!

看一下lea指令能为我们干点什么呢?

lea eax, [12345678h]

eax的最后结果是什么呢?正确答案是12345678h.

假设 EBP = 1

lea eax, [ebp + 12345678h]

结果是123456789h....呵呵比较一下:

lea eax, [ebp + 12345678h] ;6 bytes

==========================

mov eax, 12345678h ;5 bytes

add eax, ebp ;2 bytes

5) 看看:

mov eax, 12345678h ;5 bytes

add eax, ebp ;2 bytes

imul ecx, 4 ;3 bytes

add eax, ecx ;2 bytes

6) 用lea来进行一些计算我门将从体积上得到好处:

lea eax, [ebp+ecx*4+12345678h] ;7 bytes

速度上一条lea指令更快!不影响标志位...记住下面的格式,在许多地方善用他们你可以节省时间和空间.

OPCODE <SIZE PTR> [BASE + INDEX*SCALE + DISPLACEMENT]

10.下面是关于病毒重定位优化的,惧毒人士请绕行...

下面的代码你不应该陌生

1) call gdelta

gdelta: pop ebp

sub ebp, offset gdelta

在以后的代码中我们这样使用delta来避免重定位问题

lea eax, [ebp + variable]

这样的指令在应用内存数据的时候是不可避免的,如果能优化一下,我门将会得到数倍收益,打开你的sice或者trw或者ollydbg等调试器,看看:

3) lea eax, [ebp + 401000h] ;6 bytes

假如是下面这样

4) lea eax, [ebp + 10h] ;3 bytes

也就是说如果ebp后面变量是1字节的话,总的指令就只有3字节

修改一下最初的格式变为:

5) call gdelta

gdelta: pop ebp

在某些情况下我们的指令就只有3字节了,可以节省3字节,嘿嘿,让我们看看:

6) lea eax, [ebp + variable - gdelta] ;3 bytes

和上面的是等效的,但是我们可以节省3字节,看看CIH...

11.其他技巧:

如果EAX小于80000000h,edx清0:

--------------------------------------------------

1) xor edx, edx ;2 bytes, but faster

2) cdq ;1 byte, but slower

我一直使用cdq,为什么不呢?体积更小...

下面这种情况一般不要使用esp和ebp,使用其他寄存器.

-----------------------------------------------------------

1) mov eax, [ebp] ;3 bytes

2) mov eax, [esp] ;3 bytes

3) mov eax, [ebx] ;2 bytes

交换寄存器中4个字节的顺序?用bswap

---------------------------------------------------------

mov eax, 12345678h ;5 bytes

bswap eax ;2 bytes

;eax = 78563412h now

Wanna save some bytes replacin' CALL ?

---------------------------------------

1) call _label_ ;5 bytes

ret ;1 byte

2) jmp _label_ ;2/5 (SHORT/NEAR)

如果仅仅是优化,并且不需要传递参数,请尽量用jmp代替call

比较 reg/mem 时如何节省时间:

------------------------------------------

1) cmp reg, [mem] ;slower

2) cmp [mem], reg ;1 cycle faster

乘2除2如何节省时间和空间?

------------------------------------------------------------

1) mov eax, 1000h

mov ecx, 4 ;5 bytes

xor edx, edx ;2 bytes

div ecx ;2 bytes

2) shr eax, 4 ;3 bytes

3) mov ecx, 4 ;5 bytes

mul ecx ;2 bytes

4) shl eax, 4 ;3 bytes

loop指令

------------------------

1) dec ecx ;1 byte

jne _label_ ;2/6 bytes (SHORT/NEAR)

2) loop _label_ ;2 bytes

再看:

3) je $+5 ;2 bytes

dec ecx ;1 byte

jne _label_ ;2 bytes

4) loopXX _label_ (XX = E, NE, Z or NZ) ;2 bytes

loop体积小,但486以上的cpu上执行速度会慢一点...

比较:

---------------------------------------------------------

1) push eax ;1 byte

push ebx ;1 byte

pop eax ;1 byte

pop ebx ;1 byte

2) xchg eax, ebx ;1 byte

3) xchg ecx, edx ;2 bytes

如果仅仅是想移动数值,用mov,在pentium上会有较好的执行速度:

4) mov ecx, edx ;2 bytes

比较:

--------------------------------------------

1) 未优化:

lbl1: mov al, 5 ;2 bytes

stosb ;1 byte

mov eax, [ebx] ;2 bytes

stosb ;1 byte

ret ;1 byte

lbl2: mov al, 6 ;2 bytes

stosb ;1 byte

mov eax, [ebx] ;2 bytes

stosb ;1 byte

ret ;1 byte

---------

;14 bytes

2) 优化了:

lbl1: mov al, 5 ;2 bytes

lbl: stosb ;1 byte

mov eax, [ebx] ;2 bytes

stosb ;1 byte

ret ;1 byte

lbl2: mov al, 6 ;2 bytes

jmp lbl ;2 bytes

---------

;11 bytes

读取常数变量,试试在指令中直接定义:

-----------------------------

...

mov [ebp + variable], eax ;6 bytes

...

...

variable dd 12345678h ;4 bytes

2) 优化为:

mov eax, 12345678h ;5 bytes

variable = dword ptr $ - 4

...

...

mov [ebp + variable], eax ;6 bytes

呵呵,好久没看到这么有趣的代码了,前提是编译的时候支持代码段的写入属性要被设置.

最后介绍未公开指令SALC,现在的调试器都支持...什么含义呢:就是CF位置1的话就将al置为0xff

------------------------------------------------------------------

1) jc _lbl1 ;2 bytes

mov al, 0 ;2 bytes

jmp _end ;2 bytes

_lbl: mov al, 0ffh ;2 bytes

_end: ...

2) SALC db 0d6h ;1 byte ;)

------------------------------------------------------------------>over...

原作者: Benny/29A

翻译改写: hume/冷雨飘心

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有