More Effective C++ 條款15 (轉)

gugu99發表於2008-07-25
More Effective C++ 條款15 (轉)[@more@]

 

條款15:瞭解異常處理的開銷:namespace prefix = o ns = "urn:schemas--com::office" />

為了在執行時處理異常,要記錄大量的資訊。無論到什麼地方,程式都必須能夠識別出如果在此處丟擲異常的話,將要被釋放哪一個;程式必須知道每一個入口點,以便從try塊中退出;對於每一個try塊,他們都必須跟蹤與其相關的catch子句以及這些catch子句能夠捕獲的異常型別。這種資訊的記錄不是沒有代價的。確保程式滿足異常規格不需要執行時的比較(runtime comparisons),而且當異常被丟擲時也不用額外的開銷來釋放相關的物件和匹配正確的catch字句。但是異常處理確是有代價的,即使你沒有使用try,throw或catch關鍵字,你同樣得付出一些代價。

讓我們先從你不使用任何異常處理特性也要付出的代價談起。你需要空間建立資料結構來跟蹤物件是否被完全構造(constructed)(參加條款10),你也需要系統時間保持這些資料結構不斷。這些開銷一般不是很大,但是當採用不支援異常的方法編譯的程式一般比支援異常的程式執行速度更快所佔空間也更小。

在理論上,你不能對此進行選擇:C++必須支援異常,也就是說,當你不用異常處理時你不能讓編譯器生產商消除這方面的開銷,因為程式一般由多個獨立生成的目標( files)組成,只有一個目標檔案不進行異常處理並不能代表其他目標檔案不進行異常處理。而且即使組成可執行檔案的目標檔案都不進行異常處理,那麼還有它們所連線的程式庫呢?如果程式的任何部分使用了異常,其它部分必須也支援異常。否則在執行時程式就不可能提供正確的異常處理。

不過這只是理論,實際上大部分支援異常的編譯器生產商都允許你自由控制是否在生成的程式碼裡包含進支援異常的內容。如果你知道你程式的任何部分都不使用try,throw或catch,並且你也知道所連線的程式庫也沒有使用try,throw或catch,你就可以採用不支援異常處理的方法進行編譯,這可以縮小程式的尺寸和提高速度,否則你就得為一個不需要的特性而付出代價。隨著時間的推移,使用異處理的程式庫開始變得普遍了,上面這種方法將逐漸不能使用,但是根據目前的開發情況來看,如果你已經決定不使用任何的異常特性,那麼採用不支援異常的方法編譯程式是一個的合理方法。同樣這對於想避開異常的程式庫來說也是一個效能最佳化的好方法,這能保證異常不會從客戶端程式傳遞程式序庫裡,不過同時這樣做也會妨礙客戶端程式重定義程式庫中宣告的虛擬,並不允許有在客戶端定義的回撥函式。

使用異常處理的第二個開銷來自於try塊,無論何時使用它,也就是無論何時你想能夠捕獲異常,那你都得為此付出代價。不同的編譯器實現try塊的方法不同,所以編譯器與編譯器間的開銷也不一樣。粗略地估計,如果你使用try塊,程式碼的尺寸將增加5%-10%並且執行速度也同比例減慢。這還是假設程式沒有丟擲異常,我這裡討論的只是在程式裡使用try塊的開銷。為了減少開銷,你應該避免使用無用的try塊。

編譯器為異常規格生成的程式碼與它們為try塊生成的程式碼一樣多,所以一個異常規格一般花掉與tyr塊一樣多的系統開銷。什麼?你說你認為異常規格只是一個規格而已,你認為它們不會產生程式碼?那麼好,現在你應該對此有新的認識了。

現在我們來到了問題的核心部分,看看丟擲異常的開銷。事實上我們不用太關心這個問題,因為異常是很少見的,這種事件的發生往往被描述為exceptional(異常的,罕見的)。80-20規則(參見條款16)告訴我們這樣的事件不會對整個程式的效能造成太大的影響。但是我知道你仍舊好奇地想知道如果丟擲一個異常到底會有多大的開銷,答案是這可能會比較大。與一個正常的函式返回相比,透過丟擲異常從函式里返回可能會慢三個數量級。這個開銷很大。但是僅僅當你丟擲異常時才會有這個開銷,一般不會發生。但是如果你用異常表示一個比較普遍的狀況,例如完成對資料結構的遍歷或結束一個迴圈,那你必須重新予以考慮。

不過請等一下,你問我是怎麼知道這些事情的呢?如果說支援異常對於大多數編譯器來說是一個較新的特性,如果說不同的編譯器異常方法也不同,那麼我如何能說程式的尺寸將增大5%-10%,它的速度也同比例減慢,而且如果有大量的異常被丟擲,程式執行速度會呈數量級的減慢呢?答案是令人驚恐的:一些傳聞和一些基準測試(benchmarks)(參見條款23)。事實是大部分人包括編譯器生產商在異常處理方面幾乎沒有什麼,所以儘管我們知道異常確實會帶來開銷,卻很難預測出開銷的準確數量。

謹慎的方法是對本條款所敘述的開銷有了解,但是不深究具體的數量。(即定性不定量  譯者注)不論異常處理的開銷有多大我們都得堅持只有必須付出時才付出的原則。為了使你的異常開銷最小化,只要可能儘量就採用不支援異常的方法編譯程式,把使用try塊和異常規格限制在你確實需要它們的地方,並且只有在確為異常的情況下(exceptional)才丟擲異常。如果你在效能上仍舊有問題,總體評估一下你的軟體以決定異常支援是否是一個起作用的因素。如果是,那就考慮選擇其它的編譯器,能在C++異常處理方面具有更高實現的編譯器。

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1007847/,如需轉載,請註明出處,否則將追究法律責任。

相關文章