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

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

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

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

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

 

// simple classapp

class MyAppObject函數

{性能

    public:測試

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

       ~MyAppObject();spa

       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」來用。若是應用允許,也要儘量減小「熱點」,減少熱點列表。

相關文章
相關標籤/搜索