天天看點

.Net Discovery系列之十二-深入了解平台機制與性能影響(下)

  上一篇文章中Aicken為大家介紹了.Net平台的垃圾回收機制、即時編譯機制與其對性能的影響,這一篇中将繼續為大家介紹.Net平台的異常捕獲機制與字元串駐留機制。

三.關于異常捕獲機制

    雖然我們已經很辛苦了,但是仍然有很多原因使代碼運作失敗,如引用null引用、索引越界、記憶體溢出、類型轉換失敗等等。這就需要我們的代碼有足夠的容錯能力,在代碼運作失敗時,及時、主動的處理這些異常。

● 機制分析

    .Net 中基本的異常捕獲與處理機制是由try…catch…finally塊來完成的,它們分别完成了異常的監測、捕獲與處理工作。一個try塊可以對應零個或多個catch塊,可以對應零個或一個finally塊。不過沒有catch的try似乎沒有什麼意義,如果try對應了多個catch,那麼監測到異常後,CLR會自上而下搜尋catch塊的代碼,并通過異常過濾器篩選對應的異常,如果沒有找到,那麼CLR将沿着調用堆棧,向更高層搜尋比對的異常,如果已到堆棧頂部依然沒有找到對應的異常,就會抛出未處理的異常了,這時catch塊中的代碼并不會被執行。是以距離try最近的catch塊将最先被周遊到。

  以下代碼:

  

.Net Discovery系列之十二-深入了解平台機制與性能影響(下)
.Net Discovery系列之十二-深入了解平台機制與性能影響(下)

代碼

try

{

Convert.ToInt32("Try");

}

catch (FormatException ex1)

string CatchFormatException = "CatchFormatException";

catch (NullReferenceException ex2)

string CatchNullReferenceException = "CatchNullReferenceException";

finally

string Finally = "Finally";

.Net Discovery系列之十二-深入了解平台機制與性能影響(下)

1

<code>  </code><code>對應IL如下: </code>

.Net Discovery系列之十二-深入了解平台機制與性能影響(下)
.Net Discovery系列之十二-深入了解平台機制與性能影響(下)

    末尾的幾行代碼揭示出IL是怎樣處理異常處理的。最後三行的每一個Item被稱作Exception Handing Clause,EHC組成Exception Handing Table,EHT與正常代碼之間由ret傳回指令隔開。

    可以看出,<b>FormatException</b>排列在EHT的第一位。

    當代碼成功執行或反之而傳回後,CLR會周遊EHT:

        1. 如果抛出異常, CLR會根據抛出異常的代碼的“位址”找到對應的EHC(<b>IL_0001 to IL_0010</b>為檢測代碼的範圍),這個例子中CLR将找到2條EHC,<b>     FormatException</b>會最先被周遊到,且為适合的EHC。

        2. 如果傳回的代碼位址在<b>IL_0001 to IL_0029</b>内,那麼還會執行<b>finally handler </b>即<b>IL_0029 to IL_0033</b>中的代碼,不管是否因成功執行代碼而返        回<b>。</b>

    事實上,catch與finally的周遊工作是分開進行的,如上文所言,CLR首先做的是周遊catch,當找到合适的catch塊後,再周遊與之對應finally;而且這個過程會遞歸進行至少兩次,因為編譯器将C#的try…catch…finally翻譯成IL中的兩層嵌套。

    當然如果沒有找到對應的catch塊,那麼CLR會直接執行finally,然後立即中斷所有線程。Finally塊中的代碼肯定會被執行,無論try是否檢測到了異常。

● 性能影響與改進建議

    異常捕獲與處理是有性能代價的,雖然這種代價在托管環境中度量起來比較困難,但是這個過程畢竟經過一系列的周遊。是以僅從性能方面考慮,一般建議有以下幾點準則:

        1.盡量給CLR一個明确的異常資訊,不要使用Exception去過濾異常

        2.盡量不要将try…catch寫在循環中

        3. try盡量少的代碼,如果有必要可以使用多個catch塊,并且将最有可能抛出的異常類型,書寫在距離try最近的位置

        4.不要隻聲明一個Exception對象,而不去處理它

       5.使用性能計數器實用工具的“CLR Exceptions”檢測異常情況,并适當優化

        6.使用成員的Try-Parse模式,如果抛出異常,那麼用false代替它

四.關于字元串拼接

    ● 機制分析

    .Net字元串型的變量有一個很特殊的機制,這個機制叫做“字元串的駐留”,其變現為字元串恒定不可改變。

    簡單的說,字元串一旦建立,就會永久駐留在記憶體中,當你修改這個字元串變量時,CLR會在記憶體中建立一個新值,并不會修改舊值,舊值隻有被垃圾回收器回收後,那部分被占用的空間才會釋放掉。

    這樣設計的目的無疑是為了提高字元串型變量的建立,因為建立字元串型變量時,CLR首先做的是在“駐留池”中周遊是否有相同的值的字元串,如果有則直接挂接變量指針,否則才會建立,但是在某些情況下,性能卻反而降低。

    ● 性能影響與改進建議

    下面通過例子簡單的說一下字元串駐留機制,假設有如下代碼:

string str = "";

string a = "str_1" + str;

a = "str_2"+ str;

    第三行C#代碼(a = "str_2"+ str;)的樣子看起來是在修改變量a的舊值"str_1",但實際上是建立了一個新的字元串"str_2",然後将變量a的指針指向了"str_2"的記憶體位址,而"str_1"依然在記憶體中沒有受到任何影響 ---這就是字元串的駐留,如果下一次有變量b的值被指派為“str_1”,CLR不會重新為這個變量重新配置設定記憶體,而是直接将該變量的指針指向“str_1”,這樣就提高了該變量的初始化速度,但是如果沒有這樣的一個b變量,那麼“str_1”就會一直占用記憶體,直至垃圾收集,這樣做浪費了記憶體資源。關于字元串的各項特性,請參考Aicken以前的文章:

    同樣ToUpper、SubString、Trim、Replace、加号連接配接等操作都會産生駐留的字元串,各位在設計程式時要特别注意。

    經常看到有的同學使用Replace替換一個網頁整個HTML的某些關鍵字,其實這樣會極大的浪費記憶體,給垃圾回收器的政策引擎以錯誤的信号,使其頻繁啟動,進而導緻性能的降低,關于政策引擎的相關話題,請參考:

    是以,有以下建議供大家參考:

        1.在用Replace做大量字元串操作時,最好僅僅對最小單元進行操作

        2在盡量少的字元串中替換,有必要時還要配合正則的使用,在替換完畢後最好根據上下文的代齡情況,手動調用一次GC的回收方法;

        3.對大規模的字元串拼接操作,則推薦使用StringBuilder

        4.能用常量指派的就别用變量。因為常量指派的字元串是在編譯器生成在“字元串常量池”的,關于常量池請參考Aicken以前的文章。

本文轉自Aicken(李鳴)部落格園部落格,原文連結:http://www.cnblogs.com/isline/archive/2010/04/14/1711677.html,如需轉載請自行聯系原作者