編譯優化

  C語言沒有彙編快,因為C語言要由編譯器翻譯為彙編, 編譯器畢竟是人造的,翻譯出來的彙編源代碼總有那麼N條 指令在更智能、更有創造性的我們看來是多餘的。

  C語言翻譯後的彙編有如下惡劣行徑:

  1. C語言偏愛內存。我們寫的彙編一般偏愛寄存器, 寄存器比內存要快很多倍。當然,寄存器的數量屈指可數, 數據多了的話也必須用內存。
  2. 內存多餘讀。假如在一個 for 循環中經常要執行 ++i 操作,編譯後的彙編可能是這樣的情形:
movl i, %eax
addl $1, %eax
movl %eax, i

即使 eax 寄存器一直存著 i 的值, C語言也喜歡操作它前先讀一下,以上3條指令濃縮為一條 incl %eax 速度就快上好幾倍了。

儘管C語言"如此不堪",但是考慮到高級語言帶來的 源碼可讀性和開發效率在數量級上的提高,我們還是原諒了它。 而且很多編譯器都有提供優化的選項, 開啟優化選項後C語言翻譯出來的彙編代碼幾近無可挑剔。

VC、VS有 Debug、Release 編譯模式, Release 下編譯後,程序的大小、執行效率都有顯著的改善。 gcc 也有優化選項,我們來看看 gcc 優化的神奇效果:

我故意寫了一個垃圾程序(math.c):

#include <stdio.h>

int main()
{
    int a=1, b=2;
    int c;

    c = a + a*b + b;

    printf("%d\n", c);
    return 0;
}

且看看不優化的情況下,彙編代碼有多麼糟糕:

編譯命令:gcc -S math.c main部分的彙編代碼:

main:
    pushl    %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    movl    $1, 28(%esp)    # 28(%esp) 是 a
    movl    $2, 24(%esp)    # 24(%esp) 是 b
    movl    24(%esp), %eax    #\
    addl    $1, %eax        #-\
    imull    28(%esp), %eax    #-eax=(b+1)*a
    addl    24(%esp), %eax    #\
    movl    %eax, 20(%esp)    #-c=(b+1)*a+b
    movl    $.LC0, %eax
    movl    20(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf
    movl    $0, %eax
    leave
    ret

彙編代碼規模龐大,翻譯水平中規中矩。 現在開啟優化選項:

編譯命令:gcc -O2 -S math.c

main:
    pushl    %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    $5, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf
    xorl    %eax, %eax
    leave
    ret

規模變為原來的一半,而且 gcc 發現了 a、b、c 變量是多餘的,直接將結果 5 傳給 printf 打印了出來 ——計算器是編譯器必備的一大技能。 初中那時候苦逼地做計算題,怎麼就不學學C語言呢O(∩_∩)O~