新聞中心

        EEPW首頁 > 嵌入式系統 > Linux操作系統中GCC的應用介紹(下)

        Linux操作系統中GCC的應用介紹(下)

        ——
        作者: 時間:2007-04-18 來源:電子產品世界 收藏

        代碼優化

        代碼優化指的是通過分析源代碼,找出其中尚未達到最優的部分,然后對其重新進行組合,目的是改善程序的執行性能。GCC提供的代碼優化功能非常強大,它通過編譯選項-On來控制優化代碼的生成,其中n是一個代表優化級別的整數。對于不同版本的GCC來講,n的取值范圍及其對應的優化效果可能并不完全相同,比較典型的范圍是從0變化到2或3。

        編譯時使用選項-O可以告訴 GCC同時減小代碼的長度和執行時間,其效果等價于-O1。在這一級別上能夠進行的優化類型雖然取決于目標處理器,但一般都會包括線程跳轉(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優化。選項-O2告訴GCC除了完成所有-O1級別的優化之外,同時還要進行一些額外的調整工作,如處理器指令調度等。選項-O3則除了完成所有-O2級別的優化之外,還包括循環展開和其它一些與處理器特性相關的優化工作。通常來說,數字越大優化的等級越高,同時也就意味著程序的運行速度越快。許多Linux程序員都喜歡使用-O2選項,因為它在優化長度、編譯時間和代碼大小之間,取得了一個比較理想的平衡點。

        下面通過具體實例來感受一下GCC的代碼優化功能,所用程序如清單3所示。

        清單3:optimize.c

        #include 
        int main(void)
        {
        double counter;
        double result;
        double temp;
        for (counter = 0;
        counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020;
        counter += (5 - 1) / 4) {
        temp = counter / 1979;
        result = counter;
        }
        printf("Result is %lf\n", result);
        return 0;
        }

        首先不加任何優化選項進行編譯:

        # gcc -Wall optimize.c -o optimize

        借助Linux提供的time命令,可以大致統計出該程序在運行時所需要的時間:

        # time ./optimize
        Result is 400002019.000000
        real 0m14.942s
        user 0m14.940s
        sys 0m0.000s

        接下去使用優化選項來對代碼進行優化處理:

        # gcc -Wall -O optimize.c -o optimize

        在同樣的條件下再次測試一下運行時間:

        # time ./optimize
        Result is 400002019.000000
        real 0m3.256s
        user 0m3.240s
        sys 0m0.000s

        對比兩次執行的輸出結果不難看出,程序的性能的確得到了很大幅度的改善,由原來的14秒縮短到了3秒。這個例子是專門針對GCC的優化功能而設計的,因此優化前后程序的執行速度發生了很大的改變。盡管GCC的代碼優化功能非常強大,但作為一名優秀的Linux程序員,首先還是要力求能夠手工編寫出高質量的代碼。如果編寫的代碼簡短,并且邏輯性強,就不會做更多的工作,甚至根本用不著優化。 {{分頁}}

        優化雖然能夠給程序帶來更好的執行性能,但在如下一些場合中應該避免優化代碼:

        ◆ 程序開發的時候 優化等級越高,消耗在編譯上的時間就越長,因此在開發的時候最好不要使用優化選項,只有到軟件發行或開發結束的時候,才考慮對最終生成的代碼進行優化。

        ◆ 資源受限的時候 一些優化選項會增加可執行代碼的體積,如果程序在運行時能夠申請到的內存資源非常緊張(如一些實時嵌入式設備),那就不要對代碼進行優化,因為由這帶來的負面影響可能會產生非常嚴重的后果。

        ◆ 跟蹤調試的時候 在對代碼進行優化的時候,某些代碼可能會被刪除或改寫,或者為了取得更佳的性能而進行重組,從而使跟蹤和調試變得異常困難。

        調試

        一個功能強大的調試器不僅為程序員提供了跟蹤程序執行的手段,而且還可以幫助程序員找到解決問題的方法。對于Linux程序員來講,GDB(GNU Debugger)通過與GCC的配合使用,為基于Linux的軟件開發提供了一個完善的調試環境。

        默認情況下,GCC在編譯時不會將調試符號插入到生成的二進制代碼中,因為這樣會增加可執行文件的大小。如果需要在編譯時生成調試符號信息,可以使用GCC 的-g或者-ggdb選項。GCC在產生調試符號時,同樣采用了分級的思路,開發人員可以通過在-g選項后附加數字1、2或3來指定在代碼中加入調試信息的多少。默認的級別是2(-g2),此時產生的調試信息包括擴展的符號表、行號、局部或外部變量信息。級別3(-g3)包含級別2中的所有調試信息,以及源代碼中定義的宏。級別1(-g1)不包含局部變量和與行號有關的調試信息,因此只能夠用于回溯跟蹤和堆棧轉儲之用。回溯跟蹤指的是監視程序在運行過程中的函數調用歷史,堆棧轉儲則是一種以原始的十六進制格式保存程序執行環境的方法,兩者都是經常用到的調試手段。

        GCC產生的調試符號具有普遍的適應性,可以被許多調試器加以利用,但如果使用的是GDB,那么還可以通過-ggdb選項在生成的二進制代碼中包含GDB專用的調試信息。這種做法的優點是可以方便GDB的調試工作,但缺點是可能導致其它調試器(如DBX)無法進行正常的調試。選項-ggdb能夠接受的調試級別和-g是完全一樣的,它們對輸出的調試符號有著相同的影響。

        需要注意的是,使用任何一個調試選項都會使最終生成的二進制文件的大小急劇增加,同時增加程序在執行時的開銷,因此調試選項通常僅在軟件的開發和調試階段使用。調試選項對生成代碼大小的影響從下面的對比過程中可以看出來:

        # gcc optimize.c -o optimize
        # ls optimize -l
        -rwxrwxr-x 1 xiaowp xiaowp 11649 Nov 20 08:53 optimize (未加調試選項)
        # gcc -g optimize.c -o optimize
        # ls optimize -l
        -rwxrwxr-x 1 xiaowp xiaowp 15889 Nov 20 08:54 optimize (加入調試選項)

        雖然調試選項會增加文件的大小,但事實上Linux中的許多軟件在測試版本甚至最終發行版本中仍然使用了調試選項來進行編譯,這樣做的目的是鼓勵用戶在發現問題時自己動手解決,是Linux的一個顯著特色。

        下面還是通過一個具體的實例說明如何利用調試符號來分析錯誤,所用程序見清單4所示。

        清單4:crash.c

        #include 
        int main(void)
        {
        int input =0;
        printf("Input an integer:");
        scanf("%d", input);
        printf("The integer you input is %d\n", input);
        return 0;
        }

        編譯并運行上述代碼,會產生一個嚴重的段錯誤(Segmentation fault)如下:

        # gcc -g crash.c -o crash
        # ./crash
        Input an integer:10
        Segmentation fault

        為了更快速地發現錯誤所在,可以使用GDB進行跟蹤調試,方法如下:

        # gdb crash
        GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
        ……
        (gdb)

        當GDB提示符出現的時候,表明GDB已經做好準備進行調試了,現在可以通過run命令讓程序開始在GDB的監控下運行:

        (gdb) run
        Starting program: /home/xiaowp/thesis/gcc/code/crash
        Input an integer:10
        
        Program received signal SIGSEGV, Segmentation fault.
        0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
        {{分頁}}

        仔細分析一下GDB給出的輸出結果不難看出,程序是由于段錯誤而導致異常中止的,說明內存操作出了問題,具體發生問題的地方是在調用_IO_vfscanf_internal ( )的時候。為了得到更加有價值的信息,可以使用GDB提供的回溯跟蹤命令backtrace,執行結果如下:

        (gdb) backtrace
        #0 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
        #1 0xbffff0c0 in ?? ()
        #2 0x4008e0ba in scanf () from /lib/libc.so.6
        #3 0x08048393 in main () at crash.c:11
        #4 0x40042917 in __libc_start_main () from /lib/libc.so.6

        跳過輸出結果中的前面三行,從輸出結果的第四行中不難看出,GDB已經將錯誤定位到crash.c中的第11行了。現在仔細檢查一下:

        (gdb) frame 3
        #3 0x08048393 in main () at crash.c:11
        11 scanf("%d", input);


         

        使用GDB提供的frame命令可以定位到發生錯誤的代碼段,該命令后面跟著的數值可以在backtrace命令輸出結果中的行首找到。現在已經發現錯誤所在了,應該將

        scanf("%d", input);
        改為
        scanf("%d", &input);

        完成后就可以退出GDB了,命令如下:

        (gdb) quit

        GDB的功能遠遠不止如此,它還可以單步跟蹤程序、檢查內存變量和設置斷點等。

        調試時可能會需要用到產生的中間結果,這時可以使用-save-temps選項,讓GCC將預處理代碼、匯編代碼和目標代碼都作為文件保存起來。如果想檢查生成的代碼是否能夠通過手工調整的辦法來提高執行性能,在編譯過程中生成的中間文件將會很有幫助,具體情況如下:

        # gcc -save-temps foo.c -o foo
        # ls foo*
        foo foo.c foo.i foo.s

        GCC 支持的其它調試選項還包括-p和-pg,它們會將剖析(Profiling)信息加入到最終生成的二進制代碼中。剖析信息對于找出程序的性能瓶頸很有幫助,是協助Linux程序員開發出高性能程序的有力工具。在編譯時加入-p選項會在生成的代碼中加入通用剖析工具(Prof)能夠識別的統計信息,而- pg選項則生成只有GNU剖析工具(Gprof)才能識別的統計信息。

        最后提醒一點,雖然GCC允許在優化的同時加入調試符號信息,但優化后的代碼對于調試本身而言將是一個很大的挑戰。代碼在經過優化之后,在源程序中聲明和使用的變量很可能不再使用,控制流也可能會突然跳轉到意外的地方,循環語句有可能因為循環展開而變得到處都有,所有這些對調試來講都將是一場噩夢。建議在調試的時候最好不使用任何優化選項,只有當程序在最終發行的時候才考慮對其進行優化。



         



        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 秦安县| 宝坻区| 桐城市| 沐川县| 井研县| 鄂托克前旗| 新竹县| 个旧市| 丹东市| 临沂市| 雅安市| 桑日县| 伊金霍洛旗| 尖扎县| 五原县| 西青区| 阿巴嘎旗| 清镇市| 凤庆县| 叙永县| 滨州市| 海阳市| 龙泉市| 原平市| 保山市| 东兴市| 滁州市| 庄浪县| 许昌市| 勃利县| 盐源县| 全椒县| 历史| 垦利县| 昭通市| 崇礼县| 济南市| 衡东县| 长白| 武安市| 准格尔旗|