.NET 4.6中的性能改進

jopen 10年前發布 | 11K 次閱讀 .NET

英文原文:Performance Improves in .NET 4.6

.NET 4.6 中帶來了一些與性能改進相關的 CLR 特性,這些特性中有一部分將會自動生效,而另外一些特性,例如 SIMD 與異步本地存儲(Async Local Storage)則需要對編寫應用的方式進行某些改動。

SIMD

Mono 團隊一直以他們對 SIMD,即單指令流多數據流特性的支持引以為傲。SIMD 是一種 CPU 指令集,它能夠在同一時間對最多 8 個值進行同一操作。而隨著 .NET CLR 版本 4.6 的推出,Windows 開發者終于也能夠使用這一特性了。

為了實際觀察一下 SIMD 的效果,可以參考一下這個示例。假設你需要通過c[i] = a[i] + b[i]這種形式對兩個數組進行相加,以得到第三個數組。通過使用 SIMD,你可以按照以下方式編寫代碼:

for (int i = 0; i < size; i += Vector.Count)
 {
     Vector v = new Vector (A,i) + new Vector (B,i);
     v.CopyTo (C,i);
 }
</div>

請注意這個循環是如何按 Vector<int>.Count 的取值進行遞增的,根據 CPU 類型的不同,它的取值可能是 4 或是8。.NET JIT 編譯器將根據 CPU 的不同生成相應的代碼,以 4 或 8 的值對數組進行批量相加。

這種方式看起來有些繁瑣,因此微軟還提供了一系列輔助類,包括:

程序集卸載

恐怕大多數開發者都不知道這一點:.NET 經常會對同一個程序集加載兩次。發生這種情況的條件是 .NET 首先加載了某個程序集的 IL 版本,隨后又加載了同一程序集的 NGEN 版本(即預編譯版本)。這種方式對于物理內存來說是相當嚴重的浪費,尤其是對諸如 Visual Studio 這樣的大型 32 位應用程序來說更為明顯。

而在 .NET 4.6 中,一旦 CLR 加載了某個程序集的 NGEN 版本,它會自動清空對應的 IL 版本所占用的內存。

垃圾回收

早先我們曾討論過.NET 4.0 中所引入的垃圾回收滯后時間模式,雖然這種方式比起讓 GC 完全停止一段時間的做法要可靠許多,但對于許多 GC 場景來說,這種方式仍算不上完整。

在 .NET 4.6 中,你將能夠通過一種更精密的方式臨時中止垃圾回收器的運作,新的 TryStartNoGCRegion 方法允許你指定在小對象以及大對象的堆中需要多少內存。

如果出現內存不足的情況,運行時將會返回 false,或是停止運行,直到通過 GC 清理得到足夠的內存為止。你可以通過為 TryStartNoGCRegion 傳入某個標記的方式控制這一行為,如果你成功地進入了某個無 GC 區域(在過程結束前不允許進行 GC),那么在過程結束時必須調用 EndNoGCRegion 方法。

在官方文檔中并沒有說明該方法是否是線程安全的,不過考慮到 GC 的工作原理,你應當盡量避免讓兩個進程同時嘗試改變 GC 狀態的做法。

對于 GC 的另一項改進是它處理 pinned 對象(即一旦分配后不可移動位置的對象)的方式。雖然在文檔中對此方面的描述有些語焉不詳,但當你固定了某個對象的位置時,通常也會固定其相鄰對象的位置。Rich Lander 在文中寫道:

GC 將以一種更優化的方式處理 pinned 對象,因此 GC 能夠將 pinned 對象周圍的內存進行更有效地壓縮。對于大量使用 pin 方式的大規模應用來說,這一改動將極大地改進應用的性能。

GC 對于如何使用較早的幾代中的內存方面也體現出更好的智能性,Rich 繼續寫道:

第 1 代對象升級為第 2 代對象的方式也得到了改進,以更有效地使用內存。在為某一代分配新的內存空間之前,GC 會先嘗試使用可用的空間。同時,在利用可用空間區域創建對象時使用了新的算法,使新分配的空間大小比起從前更接近于對象的大小。

異步本地存儲

最后一項改進與性能并沒有直接的關系,但通過有效的利用仍然能達到優化的效果。在異步 API 還沒有流行起來的年代,開發者可以利用線程本地存儲(TLS)緩存信息。TLS 對于某個特定的線程來說就像是一種全局對象,這意味著你可以直接訪問上下文信息并進行緩存,而無需顯式地傳遞某種上下文對象。

而在 async/await 模式中,線程本地存儲就變得毫無用武之地了。因為每次調用 await 的時候,都有可能跳轉至另一個線程。而且即便僥幸避開了這種情況,但其它代碼也有可能跳轉到你的線程中并干擾 TLS 中的信息。

新版本的 .NET 引入了異步本地存儲(ALS)機制以解決這一問題,ALS 在語義上等價于線程本地存儲,但它能夠隨著 await 的調用進行相應的跳轉。這一功能將通過 AsyncLocal 泛型類實現,其內部將調用 CallContext 對象用于保存數據。

來自: InfoQ

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!