Linux操作系統(tǒng)中GCC的應(yīng)用介紹(下)
代碼優(yōu)化
代碼優(yōu)化指的是編譯器通過分析源代碼,找出其中尚未達(dá)到最優(yōu)的部分,然后對其重新進(jìn)行組合,目的是改善程序的執(zhí)行性能。GCC提供的代碼優(yōu)化功能非常強大,它通過編譯選項-On來控制優(yōu)化代碼的生成,其中n是一個代表優(yōu)化級別的整數(shù)。對于不同版本的GCC來講,n的取值范圍及其對應(yīng)的優(yōu)化效果可能并不完全相同,比較典型的范圍是從0變化到2或3。
編譯時使用選項-O可以告訴 GCC同時減小代碼的長度和執(zhí)行時間,其效果等價于-O1。在這一級別上能夠進(jìn)行的優(yōu)化類型雖然取決于目標(biāo)處理器,但一般都會包括線程跳轉(zhuǎn)(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優(yōu)化。選項-O2告訴GCC除了完成所有-O1級別的優(yōu)化之外,同時還要進(jìn)行一些額外的調(diào)整工作,如處理器指令調(diào)度等。選項-O3則除了完成所有-O2級別的優(yōu)化之外,還包括循環(huán)展開和其它一些與處理器特性相關(guān)的優(yōu)化工作。通常來說,數(shù)字越大優(yōu)化的等級越高,同時也就意味著程序的運行速度越快。許多Linux程序員都喜歡使用-O2選項,因為它在優(yōu)化長度、編譯時間和代碼大小之間,取得了一個比較理想的平衡點。
下面通過具體實例來感受一下GCC的代碼優(yōu)化功能,所用程序如清單3所示。
清單3:optimize.c
|
首先不加任何優(yōu)化選項進(jìn)行編譯:
|
借助Linux提供的time命令,可以大致統(tǒng)計出該程序在運行時所需要的時間:
|
接下去使用優(yōu)化選項來對代碼進(jìn)行優(yōu)化處理:
|
在同樣的條件下再次測試一下運行時間:
|
對比兩次執(zhí)行的輸出結(jié)果不難看出,程序的性能的確得到了很大幅度的改善,由原來的14秒縮短到了3秒。這個例子是專門針對GCC的優(yōu)化功能而設(shè)計的,因此優(yōu)化前后程序的執(zhí)行速度發(fā)生了很大的改變。盡管GCC的代碼優(yōu)化功能非常強大,但作為一名優(yōu)秀的Linux程序員,首先還是要力求能夠手工編寫出高質(zhì)量的代碼。如果編寫的代碼簡短,并且邏輯性強,編譯器就不會做更多的工作,甚至根本用不著優(yōu)化。 {{分頁}}
優(yōu)化雖然能夠給程序帶來更好的執(zhí)行性能,但在如下一些場合中應(yīng)該避免優(yōu)化代碼:
◆ 程序開發(fā)的時候 優(yōu)化等級越高,消耗在編譯上的時間就越長,因此在開發(fā)的時候最好不要使用優(yōu)化選項,只有到軟件發(fā)行或開發(fā)結(jié)束的時候,才考慮對最終生成的代碼進(jìn)行優(yōu)化。
◆ 資源受限的時候 一些優(yōu)化選項會增加可執(zhí)行代碼的體積,如果程序在運行時能夠申請到的內(nèi)存資源非常緊張(如一些實時嵌入式設(shè)備),那就不要對代碼進(jìn)行優(yōu)化,因為由這帶來的負(fù)面影響可能會產(chǎn)生非常嚴(yán)重的后果。
◆ 跟蹤調(diào)試的時候 在對代碼進(jìn)行優(yōu)化的時候,某些代碼可能會被刪除或改寫,或者為了取得更佳的性能而進(jìn)行重組,從而使跟蹤和調(diào)試變得異常困難。
調(diào)試
一個功能強大的調(diào)試器不僅為程序員提供了跟蹤程序執(zhí)行的手段,而且還可以幫助程序員找到解決問題的方法。對于Linux程序員來講,GDB(GNU Debugger)通過與GCC的配合使用,為基于Linux的軟件開發(fā)提供了一個完善的調(diào)試環(huán)境。
默認(rèn)情況下,GCC在編譯時不會將調(diào)試符號插入到生成的二進(jìn)制代碼中,因為這樣會增加可執(zhí)行文件的大小。如果需要在編譯時生成調(diào)試符號信息,可以使用GCC 的-g或者-ggdb選項。GCC在產(chǎn)生調(diào)試符號時,同樣采用了分級的思路,開發(fā)人員可以通過在-g選項后附加數(shù)字1、2或3來指定在代碼中加入調(diào)試信息的多少。默認(rèn)的級別是2(-g2),此時產(chǎn)生的調(diào)試信息包括擴展的符號表、行號、局部或外部變量信息。級別3(-g3)包含級別2中的所有調(diào)試信息,以及源代碼中定義的宏。級別1(-g1)不包含局部變量和與行號有關(guān)的調(diào)試信息,因此只能夠用于回溯跟蹤和堆棧轉(zhuǎn)儲之用?;厮莞欀傅氖潜O(jiān)視程序在運行過程中的函數(shù)調(diào)用歷史,堆棧轉(zhuǎn)儲則是一種以原始的十六進(jìn)制格式保存程序執(zhí)行環(huán)境的方法,兩者都是經(jīng)常用到的調(diào)試手段。
GCC產(chǎn)生的調(diào)試符號具有普遍的適應(yīng)性,可以被許多調(diào)試器加以利用,但如果使用的是GDB,那么還可以通過-ggdb選項在生成的二進(jìn)制代碼中包含GDB專用的調(diào)試信息。這種做法的優(yōu)點是可以方便GDB的調(diào)試工作,但缺點是可能導(dǎo)致其它調(diào)試器(如DBX)無法進(jìn)行正常的調(diào)試。選項-ggdb能夠接受的調(diào)試級別和-g是完全一樣的,它們對輸出的調(diào)試符號有著相同的影響。
需要注意的是,使用任何一個調(diào)試選項都會使最終生成的二進(jìn)制文件的大小急劇增加,同時增加程序在執(zhí)行時的開銷,因此調(diào)試選項通常僅在軟件的開發(fā)和調(diào)試階段使用。調(diào)試選項對生成代碼大小的影響從下面的對比過程中可以看出來:
|
雖然調(diào)試選項會增加文件的大小,但事實上Linux中的許多軟件在測試版本甚至最終發(fā)行版本中仍然使用了調(diào)試選項來進(jìn)行編譯,這樣做的目的是鼓勵用戶在發(fā)現(xiàn)問題時自己動手解決,是Linux的一個顯著特色。
下面還是通過一個具體的實例說明如何利用調(diào)試符號來分析錯誤,所用程序見清單4所示。
清單4:crash.c
|
編譯并運行上述代碼,會產(chǎn)生一個嚴(yán)重的段錯誤(Segmentation fault)如下:
|
為了更快速地發(fā)現(xiàn)錯誤所在,可以使用GDB進(jìn)行跟蹤調(diào)試,方法如下:
|
當(dāng)GDB提示符出現(xiàn)的時候,表明GDB已經(jīng)做好準(zhǔn)備進(jìn)行調(diào)試了,現(xiàn)在可以通過run命令讓程序開始在GDB的監(jiān)控下運行:
|
仔細(xì)分析一下GDB給出的輸出結(jié)果不難看出,程序是由于段錯誤而導(dǎo)致異常中止的,說明內(nèi)存操作出了問題,具體發(fā)生問題的地方是在調(diào)用_IO_vfscanf_internal ( )的時候。為了得到更加有價值的信息,可以使用GDB提供的回溯跟蹤命令backtrace,執(zhí)行結(jié)果如下:
|
跳過輸出結(jié)果中的前面三行,從輸出結(jié)果的第四行中不難看出,GDB已經(jīng)將錯誤定位到crash.c中的第11行了?,F(xiàn)在仔細(xì)檢查一下:
|
使用GDB提供的frame命令可以定位到發(fā)生錯誤的代碼段,該命令后面跟著的數(shù)值可以在backtrace命令輸出結(jié)果中的行首找到?,F(xiàn)在已經(jīng)發(fā)現(xiàn)錯誤所在了,應(yīng)該將
|
完成后就可以退出GDB了,命令如下:
|
GDB的功能遠(yuǎn)遠(yuǎn)不止如此,它還可以單步跟蹤程序、檢查內(nèi)存變量和設(shè)置斷點等。
調(diào)試時可能會需要用到編譯器產(chǎn)生的中間結(jié)果,這時可以使用-save-temps選項,讓GCC將預(yù)處理代碼、匯編代碼和目標(biāo)代碼都作為文件保存起來。如果想檢查生成的代碼是否能夠通過手工調(diào)整的辦法來提高執(zhí)行性能,在編譯過程中生成的中間文件將會很有幫助,具體情況如下:
|
GCC 支持的其它調(diào)試選項還包括-p和-pg,它們會將剖析(Profiling)信息加入到最終生成的二進(jìn)制代碼中。剖析信息對于找出程序的性能瓶頸很有幫助,是協(xié)助Linux程序員開發(fā)出高性能程序的有力工具。在編譯時加入-p選項會在生成的代碼中加入通用剖析工具(Prof)能夠識別的統(tǒng)計信息,而- pg選項則生成只有GNU剖析工具(Gprof)才能識別的統(tǒng)計信息。
最后提醒一點,雖然GCC允許在優(yōu)化的同時加入調(diào)試符號信息,但優(yōu)化后的代碼對于調(diào)試本身而言將是一個很大的挑戰(zhàn)。代碼在經(jīng)過優(yōu)化之后,在源程序中聲明和使用的變量很可能不再使用,控制流也可能會突然跳轉(zhuǎn)到意外的地方,循環(huán)語句有可能因為循環(huán)展開而變得到處都有,所有這些對調(diào)試來講都將是一場噩夢。建議在調(diào)試的時候最好不使用任何優(yōu)化選項,只有當(dāng)程序在最終發(fā)行的時候才考慮對其進(jìn)行優(yōu)化。
評論