.NET 4.6中帶來了一些與性能改進相關的CLR特性,這些特性中有一部分將會自動生效,而另一些特性,例如SIMD與異步本地存儲(Async Local Storage)則須要對編寫應用的方式進行某些改動。php
Mono團隊一直以他們對SIMD,即單指令流多數據流特性的支持引覺得傲。SIMD是一種CPU指令集,它可以在同一時間對最多8個值進行同一操做。而隨着.NET CLR版本4.6的推出,Windows開發者終於也可以使用這一特性了。html
爲了實際觀察一下SIMD的效果,能夠參考一下這個示例。假設你須要經過c[i] = a[i] + b[i]這種形式對兩個數組進行相加,以獲得第三個數組。經過使用SIMD,你能夠按照如下方式編寫代碼:web
for (int i = 0; i < size; i += Vector.Count) { Vector v = new Vector(A,i) + new Vector(B,i); v.CopyTo(C,i); }
請注意這個循環是如何按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場景來講,這種方式仍算不上完整。app
在.NET 4.6中,你將可以經過一種更精密的方式臨時停止垃圾回收器的運做,新的TryStartNoGCRegion方法容許你指定在小對象以及大對象的堆中須要多少內存。異步
若是出現內存不足的狀況,運行時將會返回false,或是中止運行,直到經過GC清理獲得足夠的內存爲止。你能夠經過爲TryStartNoGCRegion傳入某個標記的方式控制這一行爲,若是你成功地進入了某個無GC區域(在過程結束前不容許進行GC),那麼在過程結束時必須調用EndNoGCRegion方法。async
在官方文檔中並無說明該方法是不是線程安全的,不過考慮到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對象用於保存數據。