程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 淺談C++ 異常處理的語義和性能,淺談異常處理

淺談C++ 異常處理的語義和性能,淺談異常處理

編輯:C++入門知識

淺談C++ 異常處理的語義和性能,淺談異常處理


異常處理是個十分深奧的主題,這裡只是淺論其對C++性能的影響。

在VC++中,有多個異常處理模式,三個最重要:

  • No exception handling (無異常處理)
  • C++ only (C++語言異常處理)
  • C++ 加SEH (C++語言加windows 結構異常處理機制)

異常處理每增加一個級別,都要付出時空上的代價。我們從下面簡單的C++例子著手,分析異常處理的原理及其性能:

 

// simple class

class MyAppObject

{

    public:

       MyAppObject(int id) : _myID(id) {}

       ~MyAppObject();

       int _myID;

       void DoSomething(int throwWhat) ;

}; 

// can throw 2 different exception

void MyAppObject::DoSomething(int throwWhat)

{

    printf("MyAppObject::DoSomething called for '%d'\n", _myID);

    switch (throwWhat)

    {

       case 0:

             break;

       case 1:

             this->_myID /= 0;             // exception 1

             break;

       case 2:

             throw SimpleString("error!"); // exception 2

             break;

       }

}

 // Test exception for the above class

void TestMyAppObject()

{

       printf("before try”); 

       try                                                          // line1

       {

              printf("in try”);

 

             MyAppObject so = 1;                          // line2

             SimpleString ss("test ex point one");    // line3

             so.DoSomething(1);                           // line4

 

             printf("so::ID called for '%d'\n", so._myID);

             MyAppObject so2 = 2;                       // line5

 

             printf("so2::ID called for '%d'\n", so2._myID);

             so2.DoSomething(0);                        // line6

       }

       catch(const SimpleString &e)                   // line7

       {

             //printf("something happened: %s \n", e);

       }

       catch(...)                                    //line8

       {

             //printf("something happened: %s \n", "SEH");

       }

 

第一步,我們先選擇“no exception”,並將上面line1,line7,line8注釋掉。代碼的size是:

Exe

Obj

32,256 bytes

20,931 bytes

 

然而因為line4引入一個“除0”異常,我們的程序非正常地停止了工作。這並非什麼大的災難。但是如果這是關鍵的服務器程序,這樣的結果肯定不能為客戶接受。

 

第二步,我們選擇了,C++ only flag(/EHsc)。代碼size變為:

Exe

Obj

37,888 bytes

24,959 bytes

代碼size較前面選擇增加了近20%

 

然而,這個選擇決定了如果是C++的throw產生的異常我們可以俘獲。操作系統產生的異常,比如windows SEH 異常機制產生的異常,也不能俘獲。測試時,將line1line7Line8注釋取消。

運行程序,“0”異常仍然導致程序停止。然而,將line4輸入改為2時,C++ throw 的異常被line7俘獲。

 

第三步,我們選擇“C++ 加 SHE (/EHa)”,代碼size變為:

 

Exe

Obj

37.0 KB (37,888 bytes)

28,486 bytes

 

代碼 obj size 略有變化,但是不顯著。選擇了這個後,MyAppObject::DoSomething的兩種異常都能被俘獲了。

異常處理語義

加了異常處理,程序的“工作集(working set)”, 的增長度高達20%,這是相當顯著的。關鍵的軟件部件必須考慮到這一點。那麼,運行速度會不會受到影響呢?我們先看看異常處理的語義吧。

上面的TestMyAppObject中,由於C++必須保證一旦異常出現,能“正確地”地銷毀自動變量,比如TestMyAppObject中的soss,和 so2 變量。在有異常處理的情況下,必須區分“現行程序”的“區域”和“熱點”。

比如,TestMyAppObject區域before tryin try

TestMyAppObject熱點有line2 ~ line6 (每個line都是一個熱點)。

TestMyAppObject異常處理的邏輯是:

  • 做“stack unwinding (堆棧回滾)”:
    • 如果line2出異常,無須作什麼(除非有MyAppObject 裡有部分未完成構造的成員partially constructed member 問題)。
    • 如果line3出異常,so必須銷毀。
    • 如果line4出異常,soss都必須銷毀。
    • 如果line6出異常,sossso2都須銷毀。
  • 如果找到catch,執行catch
  • 如果此函數沒有catch,繼續往上面函數,重復以上步驟  

VC++的stack unwinding實現大致如此:

異常處理邏輯可以轉換成一個靜態的jump列表(列出上面的四個熱點的jump to 地址),和一個stack_unwind()函數(堆棧回滾函數),根據當前的”熱點”,通過此列表,動態地跳到異常處裡的回滾代碼處。

綜合起來,異常處理在C++中,根據函數的auto變量的分布,必須在每個可能出現異常的函數添加上訴jump列表,導致程序size和工作集明顯增加。但是測試表明,如果不出現異常,程序的執行速度的影響是可忽略的(僅僅需要保持熱點位置),TestMyAppObject的測試結果選擇異常處理(但不出異常)反而比選擇不支持異常處理稍快。 

出現異常後,TestMyAppObject的測試結果表明,程序速度的影響可以在10%~15%以上。但是我的測試還沒有加rethrow 獲者其它異常處理邏輯,僅僅俘獲而已。

另一個有趣的問題是,函數中auto變量的分布,對“熱點列表”size的影響, 熱點太多,會導致熱點列表變得很大,所以如果可能,盡量把auto變量放在頂端:

X a, b

Y c,d

而不是

X a

// do something (1

X b;

// do something else (2

Y c;

// do yet something else (3

Y d;

因為第一種分布只有一個熱點(假設constructor 不會throw)。而第二種分布至少有三個熱點。

測試結果

測試上述TestMyAppObject函數,循環1000次的結果: 

  • 傳值0,使line4不出現異常(C++ throw),時間是0.802秒。

        傳值1,使line4出現除零異常,時間是0.832秒。

  • 傳值2,使line4出現異常(C++ throw),測試1000次測試,時間是1.043秒。

這個結果我有下列觀察:

  • C++ throw的代價明顯高於windows SEH。C++ throw 異常在上述測試的時間比不出現異常增加近20%。但是如果我們throw簡單的primitive 值,速度可能增快(讀者可以自己測試)。
  • 除零異常在這裡和無異常速度接近,但是考慮到本測試的簡單性,和實際應用中try-catch可能縱跨多個函數,會線性增加stack-unwinding的代價。所以我認為,實際結果中,如果異常出現後出現性能10%~15%下降是正常的。
  • 另外要考慮的是OS和編譯版本。VC++的異常處理比前面版本的性能大大提高了。 

總結

異常處理是C++中具有重要附加值的語言構造,為安全可靠的應用程序提供了基石。

但是它也同時具有時空兩方面的代價(trade off),我們在應用時要清楚這個方面。異常應該在“異常時”用 (好像是廢話,其實是設計思想和模式的重要一環),不要把它當作方便的“控制構造 control construct”來用。如果應用容許,也要盡可能減少“熱點”,減小熱點列表。

 


對於cocos2d-x的幾個問題

這個警告的意思是說你在代碼的某個地方執行了異常處理,比如try{}catch{}語句,但編譯選項裡面沒有支持異常處理的操作。
解決方法是:在makefile文件中加上“/EHsc”這幾個字,這是命令行命令,參數意義如下:

/EH{s|a}[c][-]
參數
a 捕獲異步(結構化)異常和同步 (C++) 異常的異常處理模型。
s 僅捕獲 C++ 異常並通知編譯器假定 extern C 函數確實引發了異常的異常處理模型。
c 如果與 s (/EHsc) 一起使用,則僅捕獲 C++ 異常並通知編譯器假定 extern C 函數從未引發 C++ 異常。/EHca 等效於 /EHa。
使用 /EHs 指定同步異常處理模型(沒有結構化異常處理異常的 C++ 異常處理)。如果使用 /EHs,那麼 catch 子句將不會捕獲異步異常。此外,在 Visual C++ 2005 中,當生成異步異常時,即使處理了異步異常也不會損壞范圍內的所有對象。在 /EHs 下,catch(...) 僅捕獲 C++ 異常。將不捕獲訪問沖突和 System.Exception 異常。
使用 /EHa 指定異步異常處理模型(具有結構化異常處理異常的 C++ 異常處理)。/EHa 可能導致映像性能較差,因為編譯器將不會積極地優化 catch 塊,即使編譯器沒有發現 throw 異常。
如果希望捕獲由 throw 以外的內容引發的異常,請使用 /EHa。

紋理確實是圖片,只不過是一張大圖,比如一個游戲中很多的人物,樹木,房屋圖片等等,這些都是一個一個的精靈,把他們都畫在一張大紙上,這張大紙就是紋理。
這樣做的原因是:進入游戲處理邏輯之前先加載這張大紙,這張大紙就在緩存裡了,以後每次要用不同精靈的時候都從這張大紙上取,速度就很快。如果不用紋理,每次生成精靈都要返回到工程目錄去查找文件,再加載,會很慢,影響性能。
總結來說就是,用紋理是緩存級別的存取,不用紋理是硬盤級別的存取;一次加載,終身受益。
一般的做法是:用專門的軟件先把單個圖片拼成一張大圖,即紋理圖。再加載紋理圖。

渲染是對顯卡而言的,是顯卡把圖片畫出來的過程。比如一個正方形放在平面坐標系中,它有大小,位置,旋轉角度,顏色,這四個屬性,所以它可以被等比例的放大縮小,平移到其他位置,繞中心點或某個點旋轉一個角度,塗上任意顏色。這四個過程就叫做渲染。

萬事開頭難,加油!

 

語義關系的類型

關系語義 Kripke 語義(也叫做關系語義或框架語義,並經常混淆於可能世界語義)是模態邏輯系統的形式語義,於 1950 年代晚期和 1960 年代早期由 Saul Kripke 建立。它後來為另一個非經典邏輯,最重要的直覺邏輯所接受。Kripke 語義的發現是非經典邏輯開發中重大突破,因為這種邏輯的模型論在 Kripke 之前實際上是不存在的。
模態邏輯的語義
對於我們的目的,模態邏輯的語言由命題變量,讀者喜歡的布爾連結詞的完備集合(比如 {→,¬} 或 {∨,∧,¬}),和模態算子 <math>\Box</math> (“必然性”)構成。對偶的模態算子 <math>\Diamond</math> (“可能性”) 定義為一個簡寫: <math>\Diamond A:=\neg\Box\neg A</math>。更多背景請參見模態邏輯。
基本定義
Kripke 框架或模態框架是 <W,R> 對,這裡的 W 是非空集合,R 是在 W 上的二元關系。W 的元素叫做節點或世界,而 R 叫做可及關系。
Kripke 模型是 <W,R,<math>\Vdash</math>> 三元組,這裡的 <W,R> 是 Kripke 框架,而 <math>\Vdash</math> 是在 W 的節點和模態公式之間的如下關系:
<math>w\Vdash\neg A</math> 當且僅當 <math>w\not\Vdash A</math>,
<math>w\Vdash A\to B</math> 當且僅當 <math>w\not\Vdash A</math> 或 <math>w\Vdash B</math>,
<math>w\Vdash\Box A</math> 當且僅當 <math>\forall u\,(w\; R\; u \Rightarrow u\Vdash A)</math>。
我們把 w <math>\Vdash</math>A 讀做 “w 滿足 A”,“A 滿足於 w”,或 “w 力迫 A”。關系 <math>\Vdash</math> 叫做“滿足關系”、“求值關系”或“力迫關系”。注意滿足關系由它在命題變量上的值唯一確定。
公式 A 在下列之中是有效的:
模型 <W,R,<math>\Vdash</math>>,如果對於所有 w ∈W 有 w <math>\Vdash</math>A,
框架 <W,R>,如果對於 <math>\Vdash</math> 的所有可能的選擇,它在 &lt......余下全文>>
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved