主题:优化你的汇编代码(想看更多请访问http://asm.icpcn.com)
来源http://asm.icpcn.com
优化你的汇编代码
原作者: 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
优化你的汇编代码
原作者: 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