條款15:了解異常處理的系統(tǒng)開銷
為了在運(yùn)行時(shí)處理異常,程序要記錄大量的信息。無論執(zhí)行到什么地方,程序都必須能夠識(shí)別出如果在此處拋出異常的話,將要被釋放哪一個(gè)對(duì)象;程序必須知道每一個(gè)入口點(diǎn),以便從try塊中退出;對(duì)于每一個(gè)try塊,他們都必須跟蹤與其相關(guān)的catch子句以及這些catch子句能夠捕獲的異常類型。這種信息的記錄不是沒有代價(jià)的。確保程序滿足異常規(guī)格不需要運(yùn)行時(shí)的比較(runtime comparisons),而且當(dāng)異常被拋出時(shí)也不用額外的開銷來釋放相關(guān)的對(duì)象和匹配正確的catch字句。但是異常處理確是有代價(jià)的,即使你沒有使用try,throw或catch關(guān)鍵字,你同樣得付出一些代價(jià)。
讓我們先從你不使用任何異常處理特性也要付出的代價(jià)談起。你需要空間建立數(shù)據(jù)結(jié)構(gòu)來跟蹤對(duì)象是否被完全構(gòu)造(constructed)(參加條款10),你也需要系統(tǒng)時(shí)間保持這些數(shù)據(jù)結(jié)構(gòu)不斷更新。這些開銷一般不是很大,但是當(dāng)采用不支持異常的方法編譯的程序一般比支持異常的程序運(yùn)行速度更快所占空間也更小。
在理論上,你不能對(duì)此進(jìn)行選擇:C++編譯器必須支持異常,也就是說,當(dāng)你不用異常處理時(shí)你不能讓編譯器生產(chǎn)商消除這方面的開銷,因?yàn)槌绦蛞话阌啥鄠€(gè)獨(dú)立生成的目標(biāo)文件(object files)組成,只有一個(gè)目標(biāo)文件不進(jìn)行異常處理并不能代表其他目標(biāo)文件不進(jìn)行異常處理。而且即使組成可執(zhí)行文件的目標(biāo)文件都不進(jìn)行異常處理,那么還有它們所連接的程序庫呢?如果程序的任何部分使用了異常,其它部分必須也支持異常。否則在運(yùn)行時(shí)程序就不可能提供正確的異常處理。
不過這只是理論,實(shí)際上大部分支持異常的編譯器生產(chǎn)商都允許你自由控制是否在生成的代碼里包含進(jìn)支持異常的內(nèi)容。如果你知道你程序的任何部分都不使用try,throw或catch,并且你也知道所連接的程序庫也沒有使用try,throw或catch,你就可以采用不支持異常處理的方法進(jìn)行編譯,這可以縮小程序的尺寸和提高速度,否則你就得為一個(gè)不需要的特性而付出代價(jià)。隨著時(shí)間的推移,使用異處理的程序庫開始變得普遍了,上面這種方法將逐漸不能使用,但是根據(jù)目前的軟件開發(fā)情況來看,如果你已經(jīng)決定不使用任何的異常特性,那么采用不支持異常的方法編譯程序是一個(gè)性能優(yōu)化的合理方法。同樣這對(duì)于想避開異常的程序庫來說也是一個(gè)性能優(yōu)化的好方法,這能保證異常不會(huì)從客戶端程序傳遞進(jìn)程序庫里,不過同時(shí)這樣做也會(huì)妨礙客戶端程序重定義程序庫中聲明的虛擬函數(shù),并不允許有在客戶端定義的回調(diào)函數(shù)。
使用異常處理的第二個(gè)開銷來自于try塊,無論何時(shí)使用它,也就是無論何時(shí)你想能夠捕獲異常,那你都得為此付出代價(jià)。不同的編譯器實(shí)現(xiàn)try塊的方法不同,所以編譯器與編譯器間的開銷也不一樣。粗略地估計(jì),如果你使用try塊,代碼的尺寸將增加5%-10%并且運(yùn)行速度也同比例減慢。這還是假設(shè)程序沒有拋出異常,我這里討論的只是在程序里使用try塊的開銷。為了減少開銷,你應(yīng)該避免使用無用的try塊。
編譯器為異常規(guī)格生成的代碼與它們?yōu)?span>try塊生成的代碼一樣多,所以一個(gè)異常規(guī)格一般花掉與try塊一樣多的系統(tǒng)開銷。什么?你說你認(rèn)為異常規(guī)格只是一個(gè)規(guī)格而已,你認(rèn)為它們不會(huì)產(chǎn)生代碼?那么好,現(xiàn)在你應(yīng)該對(duì)此有新的認(rèn)識(shí)了。
現(xiàn)在我們來到了問題的核心部分,看看拋出異常的開銷。事實(shí)上我們不用太關(guān)心這個(gè)問題,因?yàn)楫惓J呛苌僖姷模@種事件的發(fā)生往往被描述為exceptional(異常的,罕見的)。80-20規(guī)則(參見條款16)告訴我們這樣的事件不會(huì)對(duì)整個(gè)程序的性能造成太大的影響。但是我知道你仍舊好奇地想知道如果拋出一個(gè)異常到底會(huì)有多大的開銷,答案是這可能會(huì)比較大。與一個(gè)正常的函數(shù)返回相比,通過拋出異常從函數(shù)里返回可能會(huì)慢三個(gè)數(shù)量級(jí)。這個(gè)開銷很大。但是僅僅當(dāng)你拋出異常時(shí)才會(huì)有這個(gè)開銷,一般不會(huì)發(fā)生。但是如果你用異常表示一個(gè)比較普遍的狀況,例如完成對(duì)數(shù)據(jù)結(jié)構(gòu)的遍歷或結(jié)束一個(gè)循環(huán),那你必須重新予以考慮。