[英]C++ Exception Throw/Catch Optimizations
在我看來,如果你有一些像這樣的C ++代碼:
int f()
{
try {
if( do_it() != success ) {
throw do_it_failure();
}
} catch( const std::exception &e ) {
show_error( e.what() );
}
}
C ++編譯器應該能夠優化throw並捕獲幾乎簡單的goto。
但是,從我查看反匯編和單步執行代碼的經驗來看,編譯器總是跳過非常混亂的異常處理庫。
他們為什么這樣做? 是否存在一些阻止優化的語言要求? 如果它是:
int f()
{
try { throw std::runtime_error("Boo!"); }
catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
}
為什么編譯器不只是重寫為
int f()
{
std::cout << "Boo!" << std::endl;
}
因為在拋出do_it_failure();
之前, do_it()
可能拋出不同的異常do_it_failure();
至於你的第二個例子,編譯器可以做到這一點,但它必須被視為一個特例,那么為什么還要為這種病態案例而煩惱呢?
他們為什么這樣做?
因為C ++例外適用於, 特殊情況下的特殊情況和性能並不重要。
考慮到這一點,設計了C ++的異常,確保編譯器供應商可以在沒有異常拋出的常見情況下提供接近最優的性能,但是在拋出異常的奇怪情況下,代價可能性能更差。
從一開始就鼓勵用戶僅在特殊情況下使用異常,並且鼓勵實現者優化無異常情況(析構函數地址必須存儲在某處以便在異常發生時調用析構函數),代價是異常案件。
雖然實施者當然可以花費資源來優化奇怪的例外情況,但大多數用戶都不喜歡這樣,因為總是有更重要的東西需要改進。
我認為,如果沒有錯誤,接受的答案是非常缺乏信息的,所以即使經過這么多年,我覺得有必要提供正確的答案。
推測為什么編譯器實現者選擇不對任何特定功能付出努力只是......好猜測。 僅在特殊情況下拋出異常的事實通常不被認為是不優化此類代碼性能的理由。 相反,盡管拋出代碼並沒有以非拋出代碼為代價進行優化,但拋出和處理異常的例外情況仍然非常謹慎地進行了優化。
此外,這段代碼可能感覺如此做作,不值得考慮,但事實並非如此:它可能來自內聯和優化更復雜的代碼,並且優化它可能導致更簡單的代碼,允許其他優化傳遞觸發,或包含進一步內聯的函數。 無論原始代碼看起來多么做作,這些優化傳遞都是正確的,有效的實現,總是值得至少考慮。 否則,即使是死代碼消除的基本過程也會被避免,因為“死代碼不應該首先寫入”。 這顯然不是這樣的。
因此,我只是不同意接受的答案。 應該異常拋出異常的事實並不是未對此代碼進行優化的原因。
原因純粹是技術性的,並在clang開發郵件列表的電子郵件中進行了解釋: http : //lists.llvm.org/pipermail/cfe-dev/2015-March/042035.html
總而言之,該語言允許在catch
塊內調用的代碼在“沒有看到”異常對象的任何點重新拋出異常:
void g() { throw; }
因此,考慮OP代碼:
int f()
{
try { throw std::runtime_error("Boo!"); }
catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
}
對於編譯器而言, e.what()
或operator<<
的兩次調用可能會重新拋出異常,因此優化掉異常處理代碼會破壞程序的語義。
確保不是這種情況需要“整個程序知識”,如上面的電子郵件中所述。 即使是簡單的情況下, 可以優化,如:
int func() {
try {
throw 42;
}catch(int x) {
return x;
}
}
上面的代碼可以轉換為return 42
。 沒有技術原因阻礙它。
盡管如此,大多數常見的編譯器都沒有這樣做( godbolt )。 這一次,我們可以從實際來源,上面鏈接的電子郵件,Clang開發人員(我們不能為其他編譯器說什么)不認為這個優化是值得的, 可能是因為它只適用於不執行的catch
塊函數調用。
無論如何,該消息沒有說明他們是否會接受這樣做的補丁。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.