2010年2月8日 星期一

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,記住!
       
下一個問題,向堆疊中壓入70

        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是指標,你想給每個doword1,看看如何作:
        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 [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,edx0:
        --------------------------------------------------

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

        2)      cdq                                  ;1 byte, but slower

       
我一直使用cdq,為什麼不呢?體積更小...


       
下面這種情況一般不要使用espebp,使用其他寄存器.
        -----------------------------------------------------------

        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


       
22如何節省時間和空間?
        ------------------------------------------------------------
        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...
 轉貼來源為:http://www.pediy.com/bbshtml/BBS5/pediy50098.htm

2010年2月3日 星期三

檔案上傳

將檔案上傳至"資料庫":

function do_saveofficehour($teapro_id)
{
$db = init_db();
if( !empty($_FILES['teapro_officehour']['name']) )
{
$filepath = $_FILES['teapro_officehour']['tmp_name'];
$filename = $_FILES['teapro_officehour']['name'];
$filesize = $_FILES['teapro_officehour']['size'];
$filetype = $_FILES['teapro_officehour']['type'];

//判斷上傳檔案類型是否正確正確才能上傳
$doc_type_name = get_file_ext($filename);
$doc_filename = $teapro_id."_officehour".".".$doc_type_name;
$pass_doc_type2 = array('txt', 'doc', 'pdf');
if(in_array($doc_type_name, $pass_doc_type2))
{
//取出目前Server能夠上傳檔案的上限
$max_upload_filesize = ini_get('upload_max_filesize')*1024*1024;
if( $filesize > 0 AND $filesize <= $max_upload_filesize)
{
$filecontent = addslashes( fread( fopen( $filepath, "r"), $filesize));
$db->query("UPDATE mod_teacher_profile SET teapro_officehour_name = '$doc_filename', teapro_officehour = '$filecontent', teapro_officehour_type = '$filetype' WHERE teapro_id = $teapro_id");
}
}
}
}

將檔案從"資料庫"下載:

function show_officehour()
{
$db = init_db();
$teapro_id = db_escape($_GET['teapro_id']);
//$message = _('沒有這個檔案');
$results = $db->get_results( "SELECT teapro_officehour_name, teapro_officehour, teapro_officehour_type FROM mod_teacher_profile WHERE teapro_id = '$teapro_id'" );
if( isset($results) )
{
ob_start();
foreach($results as $row)
{
$filename = $row->teapro_officehour_name;
$ie = strnatcasecmp("MSIE" , $_SERVER['HTTP_USER_AGENT']);
if ($ie != -1) $filename = rawurlencode($filename);
Header("Content-type: $row->teapro_officehour_type");
Header("Content-Disposition: attachment; filename=\"$filename\"");
Header("Cache-Control: cache, must-revalidate");
echo $row->teapro_officehour;
break;
}
ob_flush();
}
}

將檔案上傳至"實體位置":

function do_savelessondate($tea_crs_id)
{
$db = init_db();
$file_field = empty($_FILES['tea_crs_file']) ? 'ch_tea_crs_file' : 'tea_crs_file' ;
if( !empty($_FILES[$file_field]['name']) )
{
$filepath = $_FILES[$file_field]['tmp_name'];
$filename = $_FILES[$file_field]['name'];
$filesize = $_FILES[$file_field]['size'];
$filetype = $_FILES[$file_field]['type'];

//判斷上傳檔案類型是否正確正確才能上傳
$doc_type_name = get_file_ext($filename);
$doc_filename = $tea_crs_id."".".".$doc_type_name;
$pass_doc_type2 = array('txt', 'doc', 'pdf','xls');
if(in_array($doc_type_name, $pass_doc_type2))
{
//取出目前Server能夠上傳檔案的上限
$max_upload_filesize = ini_get('upload_max_filesize')*1024*1024;
if( $filesize > 0 AND $filesize <= $max_upload_filesize)
{
if(! is_dir($this->upload_path)) // 如果 upload/teacher 不存在
mkdir($this->upload_path, 0700);
$file_path = $this->upload_path.$doc_filename;
move_uploaded_file(iconv("utf-8", "big5",$_FILES[$file_field]['tmp_name']), iconv("utf-8", "big5", $file_path));
$sql = "UPDATE mod_teacher_course SET tea_crs_lessonfile_name ='$filename', tea_crs_lessonfile_type='$doc_type_name' WHERE tea_crs_id = $tea_crs_id";
$db->query($sql);
}
}else{
return_notify('不支援該類檔案類型上傳');
}
}
}

將檔案從"實體位置"下載:

public static function download_xml()
{
$file_name="teacher_ex";
$Download_Filename = "./upload/teacher/".$file_name.".xml";
if(file_exists($Download_Filename) == false)
{
rename("./modules/teacher/".$file_name.".xml","./upload/teacher/".$file_name.".xml");
}


$Download_Filename = "./upload/teacher/".$file_name.".xml";
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-Type:application/vnd.ms-excel");
$user_agent = strtolower ($_SERVER["HTTP_USER_AGENT"]);
header( "Content-Disposition: attachment; filename=".$file_name.".xml");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($Download_Filename));
$handle = fopen($Download_Filename, "r");
$contents = fread($handle, filesize($Download_Filename));

print_r($contents);
fclose($handle);
flush();
}


記得要設定 $this->upload_path,範例如下:

function __construct() {
$this-&gt;upload_path = UPLOAD_PATH. '/activity';
}

2010年2月2日 星期二

簡單的Blogger code block的方法(貼程式碼)

轉錄自:http://warrickdodo.blogspot.com/2007/02/code-display-block-fixes-strange-ie.html
只要加入以下:


code, .code {
display: block; /* fixes a strange ie margin bug */
font-family: Courier New;
font-size: 8pt;
overflow:auto;
background: #f0f0f0 url(http://klcintw.images.googlepages.com/Code_BG.gif) left top repeat-y;
border: 1px solid #ccc;
padding: 10px 10px 10px 21px;
max-height:200px;
line-height: 1.2em;
}



使用方法可以用兩種:


<code>我的程式碼</code>


或在任何的HTML tag上加上"code"的class即可.

需要注意的是, 顯示的結果仍然是HTML-based的東西, 記得要escape<,>等字元

用 javascript 限制上傳檔案類型

// ========== JavaScript ==========

function doc_preview(x) 

{

var ext = x.value;

var ext_length = ext.lastIndexOf('.');

ext = ext.substring(ext_length+1,ext.length);  // get file type

ext = ext.toLowerCase();

if(ext != 'txt' && ext != 'doc' && ext != 'pdf' && ext != 'xls')

{

document.getElementById(x.id).value="";  // for FF

document.getElementById(x.id).outerHTML = '<input type="file" name="' + x.id + '" id="' + x.id + '" onChange="doc_preview(this)" />'; // for IE & Other

alert("<{$T_no_rule}>");

}

if(!x || !x.value) return;  

}

//========== HTML ==========


<input type="file" name="tea_crs_file" id="tea_crs_file" onChange="doc_preview(this)" />

記得上傳檔案的 <form/> 要加上 <form enctype="multipart/form-data"/> .