科研和工程中的C++編程3-代碼優(yōu)化_第1頁
科研和工程中的C++編程3-代碼優(yōu)化_第2頁
科研和工程中的C++編程3-代碼優(yōu)化_第3頁
科研和工程中的C++編程3-代碼優(yōu)化_第4頁
科研和工程中的C++編程3-代碼優(yōu)化_第5頁
已閱讀5頁,還剩24頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

科研和工程中的C++編程代碼優(yōu)化浙江大學計算機學院

袁昕代碼優(yōu)化剖析(profile)

利用工具分析程序代碼,調(diào)用頻率,計算時間等。要防止:

(1)優(yōu)化了錯誤的代碼:若一個程序的主要指標不是效率,那么一切花在使其更高效上的時間都是浪費??恐庇X來判斷哪段代碼的主要指標是效率是不可信的,只有直接去測量。

(2)程序員經(jīng)常“優(yōu)化”到降低了代碼的速度。這在C++是一個典型問題,一個簡單的指令行可能會產(chǎn)生巨大數(shù)量的機器代碼,應當經(jīng)常檢查編譯器的輸出,并且剖析之*。代碼優(yōu)化構造和析構:

未經(jīng)認真設計的程序經(jīng)?;ㄙM不少時間在調(diào)用構造函數(shù),拷貝對象以及初始化臨時對象等等。代碼優(yōu)化即便arg為0,也付出了調(diào)用Object的構造函數(shù)的代價。特別是如果arg經(jīng)常是0,并且Object本身還分配內(nèi)存,這種浪費會更加嚴重。顯然的解決方案就是把obj的定義移到判斷之后。在循環(huán)中定義復雜變量要注意,若在循環(huán)中按照除非需要否則不構造的原則構造了復雜的對象,那么在每一次循環(huán)的時候都要付出一次構造的代價。最好在循環(huán)外構造之,并只構造一次。如果一個函數(shù)在內(nèi)循環(huán)中被調(diào)用,而該函數(shù)在棧內(nèi)構造了一個對象,那么可以在外部構造并傳遞一個引用給它。代碼優(yōu)化盡量采用初始化列表,類中的成員類對象,如果在構造方法里初始化,一般是使用了等號操作符,這樣就等于是調(diào)用了無參數(shù)構造方法一次,再調(diào)用一次等號操作符。而使用初始化列表則直接調(diào)用其拷貝構造方法*。要前自增不要后自增(即要++A不要A++),后自增會產(chǎn)生臨時對象,調(diào)用構造方法。對于整數(shù),這沒有額外的負擔,但對于用戶自定義類型,這就是浪費。代碼優(yōu)化盡量少使用有返回值的操作符,如Toperator+(constT&)等,它返回時將構造臨時對象,調(diào)用構造方法??蓢L試C++0x引入的新的語法:右值引用。操作符+-*/%等都需要被設計成有返回值的方法,因此方法體內(nèi)部出錯時,只能以拋出異常的方式來通知調(diào)用者。代碼優(yōu)化構造方法盡可能寫輕量級代碼,有些成員初始化可以使用兩步的方法,構造方法什么都不作,另外設計一個方法來初始化成員。要注意類型轉換會產(chǎn)生隱藏的臨時對象,所以要針對各種類型重載操作符方法。代碼優(yōu)化虛函數(shù):

虛函數(shù)的機制很簡單。為了完成一個對象的虛函數(shù)調(diào)用,編譯器訪問對象的虛函數(shù)表,獲得一個成員函數(shù)的指針,設置調(diào)用環(huán)境,然后跳轉到該成員函數(shù)的地址上。一個虛函數(shù)調(diào)用的額外負擔是虛函數(shù)表的間接指向;由于事先并不知道將要跳轉的地址,所以也有可能造成處理器不能命中Cache。代碼優(yōu)化一般的C++程序都對虛函數(shù)有大量的使用,所以主要的手段是防止在那些極其重視效率的地方的虛函數(shù)調(diào)用。可以改成內(nèi)聯(lián)函數(shù)以省去函數(shù)調(diào)用的開銷,也可以利用模版類的特點使用mix-inclass模式(混同體,設計模式書中是策略模式)。如下圖:代碼優(yōu)化代碼優(yōu)化其中的模版參數(shù)T是最終派生類。這樣在CXXX實例中調(diào)用XXX()方法時,將調(diào)用最終派生類CXXX中重載的DoXXX()方法,也做到了多態(tài),但省去了虛函數(shù)表的調(diào)用開銷。

Windows下ATL編程還可以在父類上進一步加ATL_NO_VTABLE關鍵字,如classATL_NO_VTABLECXXXImpl,目的使得父類構造方法不初始化虛函數(shù)表,從而優(yōu)化時把父類虛函數(shù)表及虛函數(shù)體從最后生成的模塊中刪除,從而減少了可執(zhí)行文件的大小,進而減少內(nèi)存頁的切換。代碼優(yōu)化在很小的、頻繁使用的類上使用任何虛函數(shù)會造成額外的負擔,這些都是不能接受的。由于繼承一般都要用到一個或幾個虛函數(shù)(至少有一個虛的析構函數(shù)),所以沒必要在小而頻繁使用的對象上使用任何繼承。當然,繼承的層次越少越好,一般3~5層足夠。代碼優(yōu)化

MFC、Qt、WxWidget、C#、Java等都使用了單根的類繼承體系,顯然它們都會有上面的問題,而且用單根繼承來描述世界有點怪異,使用多繼承組合、概念泛化、基于對象的設計來描述世界就比較自然。關于C++類對象的內(nèi)存布局,參考書有:

《深入探索C++物件模型》代碼優(yōu)化函數(shù)/方法參數(shù)的設計盡可能使用簡單的類型,這樣可以減少額外的類方法的調(diào)用。若參數(shù)類型為類,在隱式類型轉換中就會調(diào)用構造方法,造成額外的調(diào)用負擔。如:

voidFoo(std::string&str){…}

Foo(“aaa”);代碼優(yōu)化這里對Foo()的調(diào)用包括了對給定constchar*參數(shù)的構造函數(shù)的調(diào)用。在一般的實現(xiàn)中,這個構造函數(shù)執(zhí)行了一個malloc(),一個strlen(),以及一個memcpy()。由于該例子中的string沒有被更多的應用,接著就調(diào)用了析構函數(shù),即free()。這里的內(nèi)存分配完全是浪費,因為字符串“aaa”早就在程序的數(shù)據(jù)段中了,因而有它在內(nèi)存中的副本。如果Foo參數(shù)定義成constchar*,那么就沒有了上面所說的那些額外的調(diào)用。對應的較好的形式如下:

voidFoo(constchar*str){…}代碼優(yōu)化inline關鍵字的優(yōu)化功能使用內(nèi)聯(lián)函數(shù)/方法可消除函數(shù)調(diào)用的系統(tǒng)開銷,但是內(nèi)聯(lián)太多代碼可能使應用程序很大,致使虛擬內(nèi)存頁的錯誤數(shù)增加(即內(nèi)存頁交換也要時間開銷)。所以要檢查每個函數(shù)/方法,決定是否內(nèi)聯(lián),以防止生成的代碼膨脹。最終運行效率以測量的時間為準(如用性能監(jiān)視器

perfmon.exe)*。代碼優(yōu)化減少緩存未命中和頁錯誤

緩存未命中不論出現(xiàn)在內(nèi)部緩存中,還是出現(xiàn)在外部緩存中,都會降低程序的性能;而頁錯誤由于會轉到二級存儲中獲得程序指令和數(shù)據(jù),也會降低程序的性能。

為避免此問題,使用具有良好引用地址的數(shù)據(jù)結構很重要,這意味著應將相關的事物合在一起。有時看上去很棒的數(shù)據(jù)結構由于引用地址不好而變得很糟,有時正好相反。代碼優(yōu)化

(1)動態(tài)分配的鏈接表可以降低程序性能,當搜索項或者在表中遍歷到末尾時,每個跳過的鏈接都可能未命中緩存或導致頁錯誤?;诤唵螖?shù)組的表實現(xiàn)由于較好的緩存和較少的頁錯誤,實際上可能快得多,即使考慮到數(shù)組更難增長的事實,它仍然可能更快。代碼優(yōu)化

(2)使用動態(tài)分配的鏈接表的哈希表可能降低性能。通過擴展,使用動態(tài)分配的鏈接表存儲內(nèi)容的哈希表的性能可能顯著降低。事實上,在最后的分析中,通過數(shù)組的簡單線性搜索實際上可能更快(取決于具體的情況)。基于數(shù)組的哈希表(所謂的“關閉散列”)是通常具有極佳的性能但卻經(jīng)常被忽略的實現(xiàn)。代碼優(yōu)化

(3)排序和查找也要注意容器內(nèi)元素的引用地址和預期其涉及的數(shù)據(jù)。元素的引用地址連續(xù)能提高運行效率。

(4)內(nèi)存分配:盡量分配連續(xù)的內(nèi)存來使用。如果經(jīng)常執(zhí)行小的分配,可使用自定義分配策略,先分配大的內(nèi)存塊,然后使用自定義的Helper函數(shù)從該塊分配需要的小內(nèi)存。代碼優(yōu)化閱讀ATL和STL中的集合類(array,list,map,hash,red-blackmap)的實現(xiàn)代碼,并對比它們使用內(nèi)存的方式,分析其效率。再和數(shù)據(jù)結構書上的集合類的實現(xiàn)進行對比,以及對比自己的數(shù)據(jù)結構實現(xiàn)和C++課程作業(yè),并得到結論。代碼優(yōu)化

(5)較小的工作集(由模塊文件,即程序集大小來決定)意味著更好的引用地址、更少的頁錯誤和更多的緩存命中。進程工作集是操作系統(tǒng)為測量引用地址而直接提供了最接近的尺度。

動態(tài)庫模塊盡量減少引出函數(shù)的數(shù)目,若確實較多,windows下可以通過def文件中對每個函數(shù)加NONAME屬性去掉函數(shù)名字,只存儲序數(shù),從而減小dll文件大小。代碼優(yōu)化對于確定不會拋出異常的函數(shù)和方法,在函數(shù)/方法定義的末尾加上throw()說明,可以讓編譯器優(yōu)化調(diào)用此函數(shù)/方法的代碼。此時要確保此函數(shù)/方法不拋異常,就要自己檢查函數(shù)體內(nèi)每個調(diào)用,可能拋出異常的,要自己在該函數(shù)/方法中捕獲該異常。如有可能,使用編譯器支持的內(nèi)部函數(shù)。不能忽略任何編譯信息,包括警告信息。代碼優(yōu)化整數(shù)運算:

對于某些整數(shù)運算,可以用與、或、非、移位、加減等操作來替代較耗時的乘法、除法、冪指數(shù)、對數(shù)等運算。如:代碼優(yōu)化上面的代碼求得不小于無符號整數(shù)x的2的冪。一般的做法是需要做一個循環(huán),不斷除2到0,根據(jù)循環(huán)次數(shù)計算冪。而上述代碼顯然生成的代碼更簡單,運算更快速。參考書:《高效程序的奧秘》代碼優(yōu)化配置編譯器優(yōu)化選項:

如:確保連接器配置為去除無用的函數(shù)和類,并設置為尺寸最小化而不是速度最大化(由于Cache命中的提高,會產(chǎn)生更好的運行效果)(注意在使用這項設置時檢查instrinsic功能是否也處于打開狀態(tài)),浮點優(yōu)化選項等。

使用異常會對性能造成影響,所以要綜合考慮程序魯棒性和運行效率,針對程序不同地方的不同要求做出合適的選擇。代碼優(yōu)化編譯選項若啟用運行時類型信息(RTTI),編譯器會為每一個類產(chǎn)生一些靜態(tài)信息。RTTI一般來說是缺省啟用的,這樣我們的代碼可以調(diào)用dynamic_cast以及檢測一個對象的類型??梢钥紤]完全禁止使用RTTI和dynamic_cast以節(jié)省空間(有時候dynamic_cast在某些實現(xiàn)中需要付出很高的代價)。另一方面,當真的需要有基于類型的不同行為的時候,增加一個不同行為的虛函數(shù)是更好的面向對象設計(注意static_cast與此不同,它的效率和C語言的類型轉換一樣)。代碼優(yōu)化注意開發(fā)時使用的庫以及第三方庫(如boost)、共享庫:要了解其系統(tǒng)開銷,有源代碼的可以調(diào)試其源代碼,若無,可用工具測量其性能是否滿足時間要

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論