《CLR via C#》第四版
為什么有時候有JIT的語言會比直接編譯為機器碼的語言快?
簡而言之,就是JIT所知道的信息比那些在開發機上直接編譯為機器碼的的編譯器知道的信息更多,有的時候這些信息是如此的有用,以至于效果可以超過JIT本身的開銷和JIT編譯時間受限帶來的限制。《CLR》給出了如下三種具體的原因:
1、 JIT知道當前正在使用的CPU的特性,可以使用那些在當前CPU上具備的新指令集。而傳統編譯器只能按照開發人員的指示,限制為僅使用較通用的指令集。更重要的是,這個過程是自動的,對開發人員透明的。
2、 在執行環境中,程序中有些變量可能已經變為常量,如當前CPU核心數、當前進程是否為64位進程、進程啟動時的命令行參數,這樣程序中依賴這些變量的分支/邏輯就可以被優化。給JIT進一步優化最終代碼提供了操作空間。
3、 JIT并未只生成代碼一次,在程序運行過程中,還可以監視那些部分是程序的熱點,即被大量反復執行的代碼,對這些代碼可以根據PRofile的信息重新生成更為優化的代碼。
除此以外,我認為還有一個重要的原因是:
JIT可以跨越程序集文件的邊界來進行優化、內聯,這些程序集在編譯生成的時候可能是在不同的時間、不同的機器上,傳統編譯器對此無能為力。
checked 關鍵字的作用范圍僅在當前所在函數內,不影響checked塊中調用的函數,所以下面這段代碼不會拋異常
class Program{ static void Main(string[] args) { checked { Test(); } } private static void Test() { byte a = 100; a += 200; }}
其實想想也正常,checked關鍵字的作用是將數值運算編譯為帶檢查的IL指令,如果調用的函數在另一個程序集中,該程序集早已被編譯好,又如何改變呢?
但注意:Decimal并非基本類型,四則運算沒有IL指令對應,所以不受checked影響,其運算始終會拋異常。
值類型表示不會額外為此對象在對上分配,而值類型自己可能被包含在一個引用類型中,所以值類型未必不會在堆上。
值類型也用new關鍵字,容易給人造成誤解。
值類型可以通過顯示指定將多個值類型的字段重疊在一起。
只有C++/CLI才能獲得指向已裝箱的值類型的指針,C#只能先拆箱。
當作為模板的類型參數時,值類型會強制CLR為它專門生成一份特化的代碼,而只有引用類型的模板實例可以共享代碼,減少內存占用。
個人經驗:
引用類型new一次只有一個實例,而值類型則未必,當值類型被傳遞和修改,其行為需要仔細分析各個值類型變量的生存期,給開發人員帶來不小的負擔,這里面包括所謂的裝箱拆箱。建議只在局部范圍內或是作為只讀對象的情況下才考慮使用,因為C#碼農普遍沒有C++碼農那樣對對象生存期有明確的把握的能力,容易被豬隊友害死。
在GC回收時,某個對象即使沒超出C++意義上的生存范圍(所在的塊),但由于在下面未運行的代碼中沒有被引用所以一樣會被認為沒有被引用而被GC。在/debug模式下,對象生存期會延長到函數體結束。
using System; using System.Threading; public static class Program { public static void Main() { // Create a Timer object that knows to call our TimerCallback // method once every 2000 milliseconds. Timer t = new Timer(TimerCallback, null, 0, 2000); // Wait for the user to hit <Enter>. Console.ReadLine(); } private static void TimerCallback(Object o) { // Display the date/time when this method got called. Console.WriteLine("In TimerCallback: " + DateTime.Now); //Only once! // Force a garbage collection to occur for this demo. GC.Collect(); } }
在進程正常結束的時候CLR也會執行GC過程,并釋放對象。
對于非托管的資源,建議使用SafeHandle系列管理其句柄,其基類CriticalFinalizerObject有如下CLR級別支持的額外特性:
1、 從CriticalFinalizerObject繼承的類型首次被引用時,會JIT其析構函數,確保其在析構時不會因內存不足而失敗。
2、 在析構時,會優先析構其他不是從CriticalFinalizerObject繼承的對象,使得在普通類型的析構函數中可以使用CriticalFinalizerObject類型的對象。
3、 當整個AppDomain被強行卸載時,CriticalFinalizerObject對象的析構函數仍然會被調用。
此外:
1、 SafeHandle對象可以在P/Invoke時替代IntPtr作為參數和返回類型,確保異常安全。
2、 P/Invoke時會正確管理內部的引用計數,確保多線程引用的情況下不會被提前意外釋放。
3、 CriticalHandle是不帶引用計數的SafeHandle。
4、 SafeHandle和CriticalHandle及其子類都是抽象類,在具體場景需要通過繼承的方式使用。
dynamic類型被處理為Object+DynamicAttribute,所以不能通過Object和dynamic來實現不同的重載。
const的值會在編譯時被內聯,readonly則不會,所以未來可能需要改動的值不應該用const。
Nullable類型在裝箱時CLR會特殊處理,脫掉Nullable,即null被裝箱為null,v被裝箱為v,而不是Nullable<V>類型。
可以通過AppDomain的FirstChanceException事件監視異常被拋出,但事件回調函數不能處理這個異常。
如果一個異常沒有被CLR處理,被報告至Windows Error Reporting,那么它獲得的調用棧只能到最近一次被throw或是re-throw的位置,即re-throw對Windows Error Reporting無效,仍然會重置異常拋出點。
在Catch和Finally塊中,線程不會被Abort所中斷.
Environment.FailFast可以跳過普通的異常處理邏輯和對象Finalize方法直接結束進程。
使用反射調用時,如果拋出異常,會將該異常包裹為TargetInvocationException;dynamic不受此影響。
Constrained Execution Regions (CERs),該功能可以讓CLR預先在try塊之前“準備”一段代碼,而不是在運行過程中由于載入DLL失敗、類靜態初始化失敗等原因拋出異常。
Thread.Sleep(0)可以將CPU讓給同優先級或更高優先級的線程,而Thread.Yield可以將CPU讓給更低優先級的,介于Thread.Sleep(0)和Thread.Sleep(1)之間。
新聞熱點
疑難解答