關於C#/.NET性能
在上次的例子里面,第二次執行Console.WriteLine()方法時,會完全跳過JITCompiler編譯。因為第一次已經完全編譯為了本地CPU指令並且返回了指令在內容里的入口地址,所以這一次會直接跳轉到該方法的內存地址處執行代碼,當然也會比第一次的性能要高。
C#性能較之C/C++低在哪?
在托管環境中,完成代碼的編譯會經歷兩個過程:
1.源碼首先被編譯為IL代碼
2.執行代碼時,IL會經歷第二次編譯為本地CPU指令,這個過程需要分配更多的內存和占用CPU資源。
怎么客觀地看待這個部分?
1.首先必須承認二次編譯的確損害了性能,並且分配了動態內存。分配動態內存意味着當程序終止時,編譯的代碼會被丟棄。再次運行程序或啟動兩個程序的實例,JIT編譯器會再次將IL編譯為本地CPU指令。非托管語言(C/C++)編譯器會針對具體的CPU平台編譯,調用時直接執行。
2.對大多數程序而言,都會反復多次調用同一個方法。這些方法僅僅在第一次被調用時會進行二次編譯,從而降低了性能。其實,可能有時在一個方法里面執行的時間比調用一個方法的時間長的多,所以即使是第一次調用需要消耗性能,可能相對於在整個方法執行的時間來說只是占用了一小部分比例。
3.微軟做了很多優化的工作來保證將性能的損耗降到最低。例如,在IL編譯為本地CPU指令時,編譯器能夠比非托管編譯器知道更多關於執行環境的信息。下面列舉托管編譯器勝過非托管編譯器的幾個方面:
①JIT編譯器能夠判定程序是否運行在Intel Pentium 4 CPU上,並且利用Pentium 4CPU提供的任何專用的指令的優勢來生成本地CPU指令。通常,非托管程序會針對最普遍CPU情況編譯,從而避免使用CPU提供的專用指令來提升性能。
②JIT編譯器能夠確定當一個測試在機器上運行時始終為false的情形。例如:if(numberOfCPUs>1){...}.如果宿主機器只有一個CPU,那么這段代碼能夠讓JIT編譯器不生成任何CPU指令。這種情形能夠讓本地CPU指令得到一些微調,從而讓代碼更小執行更快。
認識IL
IL是基於棧的,所有IL指令通過push操作到執行棧,並且通過pop操作將結果出棧。由於IL沒有提供操作寄存器的指令,很容易讓人們創造出針對CLR的語言和編譯器。IL指令也是無類型的。例如,IL提供的Add指令(將最后兩個操作push到棧)並沒有兩個針對32位和64位的Add指令。當Add指令執行時,它會決定棧上操作的類型並執行適合的操作。
IL的優點
1.從底層的CPU抽象出來
2.健壯性和安全性:IL編譯為本地CPU指令時,CLR執行了一個審核過程,檢查高級IL代碼確保它們是安全的,例如方法被調用時,參數的個數和類型是否正確;如果有返回值,那么檢查方法是否有return語句等等。托管模塊的元數據包含了審核過程需要的所有方法和類型信息。在Windows中,每一個進程有它自己的虛擬地址空間,這很有必要,因為你不能信任一個程序的代碼。程序完全有可能從一個未驗證的內存地址讀取或寫入,放到獨立的地址空間增強了程序的健壯性和穩定性。而且這樣可以在一個Windows虛擬地址空間運行多個托管程序。
3.由於進程會占用Windows的資源,進程越多占用的越多。多個托管程序可以運行在一個操作系統進程從而提升了性能。
CLR怎么實現在一個進程運行多個托管程序?
每一個托管程序在一個應用程序域執行。默認情況下,每一個托管的exe文件運行具有一個應用程序域的獨立地址空間,CLR的宿主進程能夠運行多個應用程序域。
了解不安全代碼
微軟C#編譯器默認生成的是安全代碼——代碼是經過安全審核的。C#編譯器也允許開發這寫不安全的代碼——直接操作內存地址以及在內存中的字節。這個功能在我們與非托管代碼交互時非常有用。所有方法在包含不安全代碼時,必須標記unsafe。當JIT編譯器試圖編譯一段不安全代碼時,它會檢查該代碼是否通過設置System.Security.Permissions.SecurityPermissionFlag’s SkipVerification標志授權。如果設置了該標志位,CLR會信任該段代碼並期望直接地址和字節操作不會引起任何損害。如果沒有設置會拋異常。
認識公共類型系統CTS
1.CTS:描述類型的定義及行為。
2.CTS規范聲明了一個類型能夠包含0個或多個成員:字段,方法,屬性,事件。
3.CTS也規定類型及其成員的可訪問性,下面列舉出來:
Private:同一個類里面可以訪問
Family:能夠被派生類里面的成員訪問,而不用考慮是否在同一個程序集里里面。C#里面用Protected修飾
Family and assembly:能夠被在同一個程序集里面的派生類成員訪問。C#及VB沒有提供該訪問控制修飾,IL匯編語言是可以的。
Assembly:同一個程序集可以訪問。C#里面用internal修飾
Family or assembly:能夠被派生類訪問(不管是否在同一個程序集),也能夠被同一個程序集的任何類型訪問(不用考慮是否是派生類)。C#用protected internal修飾
public:沒有限制。
4.CTS定義了類型繼承的規則,虛方法,對象生命周期等等。
5.CTS規定所有類型必須從System.Object繼承。Object是定義在System命名空間下的,是所有類型的根,這樣可以保證所有的類型具有一個最小化的行為集合。System.Object定義下面幾種行為:①兩個實例的比較 ②獲取實例的哈希代碼 ③查詢實例的真實類型 ④執行類型的淺復制 ⑤獲取實例對象當前狀態的字符串表示
注 《CLR via C#》(Jeffrey Richter著)——.NET 界的經典之作,讀的過程寫點筆記跟大家分享,我也推薦大家看英文版,能夠直接領會原意