.Net Discovery系列之-深入理解平臺機制與性能影響(下)

作者: Aicken(李鳴)  來源: 博客園  發布時間: 2011-04-24 21:51  閱讀: 919 次  推薦: 0   原文鏈接   [收藏]  
摘要:上一篇文章中Aicken為大家介紹了.Net平臺的垃圾回收機制、即時編譯機制與其對性能的影響,這一篇中將繼續為大家介紹.Net平臺的異常捕獲機制與字符串駐留機制。

  三.關于異常捕獲機制

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

  ● 機制分析

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

  以下代碼:

代碼
 
try
{
Convert.ToInt32(
"Try");
}

catch (FormatException ex1)
{

string CatchFormatException = "CatchFormatException";
}

catch (NullReferenceException ex2)
{

string CatchNullReferenceException = "CatchNullReferenceException";
}

finally
{
string Finally = "Finally";
}
 對應IL如下:
.method private hidebysig instance void Form1_Load(object sender,
 class [mscorlib]System.EventArgs e) cil managed
{
 // Code size 53 (0x35)
 .maxstack 1
 .locals init ([0] class [mscorlib]System.FormatException ex1,
 [1] string CatchFormatException,
 [2] class [mscorlib]System.NullReferenceException ex2,
 [3] string CatchNullReferenceException,
 [4] string Finally)
 IL_0000: nop
 IL_0001: nop
 IL_0002: ldstr "Try"
 IL_0007: call int32 [mscorlib]System.Convert::ToInt32(string)
 IL_000c: pop
 IL_000d: nop
 IL_000e: leave.s IL_0026
 IL_0010: stloc.0
 IL_0011: nop
 IL_0012: ldstr "CatchFormatException"
 IL_0017: stloc.1
 IL_0018: nop
 IL_0019: leave.s IL_0026
 IL_001b: stloc.2
 IL_001c: nop
 IL_001d: ldstr "CatchNullReferenceException"
 IL_0022: stloc.3
 IL_0023: nop
 IL_0024: leave.s IL_0026
 IL_0026: nop
 IL_0027: leave.s IL_0033
 IL_0029: nop
 IL_002a: ldstr "Finally"
 IL_002f: stloc.s Finally
 IL_0031: nop
 IL_0032: endfinally
 IL_0033: nop
 IL_0034: ret
 IL_0035: 
 // Exception count 3
 .try IL_0001 to IL_0010 catch [mscorlib]System.FormatException handler IL_0010 to IL_001b
 .try IL_0001 to IL_0010 catch [mscorlib]System.NullReferenceException handler IL_001b to IL_0026
 .try IL_0001 to IL_0029 finally handler IL_0029 to IL_0033
} // end of method Form1::Form1_Load
 

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

  可以看出,FormatException排列在EHT的第一位。

  當代碼成功執行或反之而返回后,CLR會遍歷EHT:

  1. 如果拋出異常, CLR會根據拋出異常的代碼的地址找到對應的EHC(IL_0001 to IL_0010為檢測代碼的范圍),這個例子中CLR將找到2條EHC, FormatException會最先被遍歷到,且為適合的EHC。

  2. 如果返回的代碼地址在IL_0001 to IL_0029內,那么還會執行finally handler IL_0029 to IL_0033中的代碼,不管是否因成功執行代碼而返 回

  事實上,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就會一直占用內存,直至垃圾收集,這樣做浪費了內存資源。

  同樣ToUpper、SubString、Trim、Replace、加號連接等操作都會產生駐留的字符串,各位在設計程序時要特別注意。

  經常看到有的同學使用Replace替換一個網頁整個HTML的某些關鍵字,其實這樣會極大的浪費內存,給垃圾回收器的策略引擎以錯誤的信號,使其頻繁啟動,從而導致性能的降低。

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

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

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

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

  4.能用常量賦值的就別用變量。因為常量賦值的字符串是在編譯器生成在字符串常量池的,關于常量池請參考Aicken以前的文章。

  相關文章:.Net Discovery系列-深入理解平臺機制與性能影響(上)

                      .Net Discovery系列之-深入理解平臺機制與性能影響 (中)

0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()