| 查看: 517 | 回复: 1 | |||
| 当前主题已经存档。 | |||
sdlj8051金虫 (著名写手)
|
[交流]
32位代码优化常识[转贴]
|
||
|
32位代码优化常识 原作者: Benny/29A 翻译改写:hume/冷雨飘心 [注意:这不是鹦鹉学舌的翻译,我尽量以我的理解传达原文的本意] 关于代码优化的文章实在太多了,遗憾的是大部分我都没有看,尽管他们就摆在我的床边(每当我要看的时候就忍不住打哈欠...嘿嘿).这篇文章较短所以翻了一下. 代码优化的含义: 代码优化的目标当然是体积小和速度快,但是在通常的情况下二者就象鱼和熊掌一样不能得兼,我们通常寻找的是这二者的折中,究竟应该偏向何方,那就得具体看我们的实际需要. 但有些常识是我们应该牢记的,下面就结合我们最常遇到的具体情况来漫谈一下: 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 [ Last edited by sdlj8051 on 2006-10-6 at 12:45 ] |
» 猜你喜欢
基金申报
已经有5人回复
基金委咋了?2026年的指南还没有出来?
已经有7人回复
国自然申请面上模板最新2026版出了吗?
已经有17人回复
纳米粒子粒径的测量
已经有8人回复
疑惑?
已经有5人回复
计算机、0854电子信息(085401-058412)调剂
已经有5人回复
Materials Today Chemistry审稿周期
已经有5人回复
溴的反应液脱色
已经有7人回复
推荐一本书
已经有12人回复
常年博士招收(双一流,工科)
已经有4人回复
sdlj8051
金虫 (著名写手)
- 应助: 0 (幼儿园)
- 贵宾: 0.1
- 金币: 1149.8
- 红花: 3
- 帖子: 2254
- 在线: 18.1小时
- 虫号: 71297
- 注册: 2005-05-30
- 专业: 电路与系统
|
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... |
2楼2006-08-23 13:56:51











回复此楼