1. 程式人生 > >.NET 4.5 效能改進概述

.NET 4.5 效能改進概述

本文介紹 Microsoft .NET Framework 4.5 的預釋出版本。 所有相關資訊均有可能發生變更。

在 Microsoft .NET Framework 團隊中,我們始終認為對開發者而言改進效能至少與新增新的執行時功能和庫 API 具有同等價值。 .NET Framework 4.5 在效能方面的投入可觀,所有應用程式方案將因此受益。 此外,因為 .NET 4.5 是 .NET 4 的更新,所以即使是 .NET 4 應用程式也可從許多對現有 .NET 4 功能的效能改進中獲益。

我們設定的目標是為不同的應用程式方案改進這些度量標準,然後設計更改以達到或超過這些標準。 在本文中,我將簡要概述我們在 .NET Framework 4.5 中所做的一些主要效能改進。

CLR

在此版本中,我們重點關注: 利用多個處理器核心來改進效能,降低垃圾回收器的延遲以及提高本機映像的程式碼質量。 以下是一些主要的效能改進功能。

多核實時 (JIT):我們持續監視低階硬體改進並與晶片供應商合作以實現最佳的硬體輔助效能。 特別是,自從多核晶片推出後,我們在自己的效能實驗室中使用了多核晶片,並且我們已做出適當的更改以利用這一特別的硬體更改;但是,最初這些更改只會為極少數客戶帶來益處。

目前,幾乎每臺 PC 至少都有兩個核心,這就使得需要多個核心的新功能迅速得到了廣泛應用。 在 .NET 4.5 開發初期,我們著手研究能否使用多個處理器核心共享 JIT 編譯任務(特別是作為應用程式啟動的一部分),從而優化總體體驗。

作為該調查的一部分,我們發現數量足夠多的託管應用程式具有閾值數最小的 JIT 編譯方法,這使投資變得值得。

此功能執行時藉助的是可能在後臺執行緒上執行的 JIT 編譯方法,而在多核計算機上,這些方法將在另一個核心上並行執行。 在理想情況下,第二個核心會快速超過應用程式的主線執行,因此對多數方法來說都是在需要時對其進行 JIT 編譯。 為了解要編譯哪些方法,此功能會生成跟蹤所執行方法的配置檔案資料,然後在稍後的執行中將通過此配置檔案資料獲得指導。 生成配置檔案資料這一要求是您與此功能進行互動的主要方式。

在儘可能少增加程式碼的情況下,您可以使用執行時的這一功能顯著縮短客戶端應用程式和網站的啟動時間。

特別是,您需要對 System.Runtime 名稱空間中 ProfileOptimization 類的兩種靜態方法進行直接呼叫。 有關詳細資訊,請參閱 MSDN 文件。 請注意,預設情況下對 ASP.NET 4.5 應用程式和 Silverlight 5 應用程式啟用此功能。

優化的本機映像:在某些版本中,我們已使您能夠通過名為“生成本機映像”(NGen) 的工具將程式碼預編譯到本機映像中。 由此得到的本機映像與使用 JIT 編譯所獲得的效果相比,通常會使應用程式的啟動速度明顯加快。 在此版本中,我們引入了名為“配置檔案導引優化”(MPGO) 的輔助工具以優化本機映像佈局,從而進一步提高效能。 MPGO 使用配置檔案導引優化技術,與之前介紹的多核 JIT 概念類似。 應用程式的配置檔案資料包括一個具有代表性的方案或者一組方案,可用於重新排列本機映像的佈局,以便將啟動時所需的方法和其他資料結構集中在一部分本機映像中,從而縮短啟動時間並減少工作集(應用程式的記憶體使用量)。 在我們自己的測試和體驗中,我們通常會看到 MPGO 為較大託管應用程式(例如,大型互動式 GUI 應用程式)帶來的益處,並且我們強烈建議按這些方式使用此工具。

MPGO 工具可為中間語言 (IL) DLL 生成配置檔案資料並將該配置檔案作為資源新增到 IL DLL 中。 NGen 工具用於在分析之後預編譯 IL DLL,並可根據配置檔案資料的狀態執行其他優化。 圖 1 顯示了相關流程。


圖 1 使用 MPGO 工具的程序流

大型物件堆 (LOH) 分配器:許多 .NET 開發人員都曾請求獲得針對 LOH 碎片問題的解決方案或者強制壓縮 LOH 的方法。 要詳細瞭解 LOH 的工作方式,可閱讀 Maoni Stephens 在 2008 年 6 月釋出的“CLR 全面透徹解析”專欄中發表的文章:msdn.microsoft.com/magazine/cc534993 總而言之,任何大小為 85,000 位元組或更大的物件都將在 LOH 上進行分配。 目前,不對 LOH 進行壓縮。 壓縮 LOH 將耗費大量時間,因為垃圾回收器將需要移動大型物件,而這是一個費用高昂的事情。 在回收 LOH 上的物件後,這些物件會在未被回收的物件之間留下一些可用空間,這將導致碎片問題。

更詳細地解釋,即,CLR 會列出沒有被清除的物件的可用列表以供日後重用這些物件,從而滿足大型物件的分配請求;相鄰的被清除物件將組成一個自由物件。 如果活動大型物件之間的這些可用記憶體碎片的大小不足以在 LOH 中進一步進行物件分配,則程式最終會結束,並且因為無法選擇壓縮,我們很快會遇到問題。 這會導致應用程式變得無法響應,並最終導致記憶體不足異常。

在 .NET 4.5 中,我們進行了一些更改以便在 LOH 中有效使用記憶體碎片,這些更改主要針對我們管理可用列表的方式。 這些更改適用於工作站和伺服器垃圾回收 (GC)。 請注意,這不會更改 LOH 物件 85,000 位元組的限制。

伺服器後臺 GC:在 .NET 4 中,我們為工作站 GC 啟用了後臺 GC。 自此以後,我們發現更快頻率的計算機的堆大小上限從幾 GB 到幾十 GB。 即使經過優化的並行回收器(例如,我們的回收器)也需要數秒鐘才能回收如此大的堆,這妨礙了應用程式執行緒數秒鐘。 伺服器的後臺 GC 為我們的伺服器回收器提供了並行回收支援。 它可最大程度地減少長時間處於阻止狀態的回收,同時繼續保持高的應用程式吞吐量。

如果您使用的是伺服器 GC,則無需執行任何操作即可利用這一新功能;伺服器後臺 GC 將自動開啟。 客戶端和伺服器 GC 的高階後臺 GC 的特徵是相同的:

  • 只有完整 GC(第 2 代)可在後臺發生。
  • 後臺 GC 不進行壓縮。
  • 前臺 GC(第 0 代/第 1 代 GC)可在進行後臺 GC 期間發生。 伺服器 GC 在專用伺服器 GC 執行緒上完成。
  • 完全阻止 GC 同樣在專用伺服器 GC 執行緒上發生。

非同步程式設計

新的非同步程式設計模型是作為 Visual Studio Async CTP 的一部分引入的,並且現在是 .NET 4.5 的一個重要部分。 您可以使用 .NET 4.5 中的這些新語言功能有效地編寫非同步程式碼。 C# 和 Visual Basic 中名為“async”和“await”的兩個新語言關鍵字支援這一新模型。 .NET 4.5 還進行了更新以支援使用這些新關鍵字的非同步應用程式。

MSDN 上的 Visual Studio 非同步程式設計門戶 (msdn.microsoft.com/vstudio/async) 是獲取關於新語言功能和支援的示例、白皮書及討論內容的絕佳資源。

平行計算庫

已在 .NET 4.5 中對平行計算庫 (PCL) 做出許多改進以增強現有 API 功能。

更快的輕型任務:System.Threading.Tasks.Task 和 Task<TResult> 類已經過優化,可在關鍵方案中使用更少的記憶體且更快地執行。 特別是,與建立任務和安排後續任務相關的案例的效能提高了多達 60%。

更多 PLINQ 查詢並行執行:PLINQ 在它認為並行執行查詢會帶來更多損失(使操作變慢)時將回退為順序執行。 這些決策是有根據的猜測且不總是完美無缺,在 .NET 4.5 中,PLINQ 將識別更多型別的可成功並行執行的查詢。

更快的併發回收:對 System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> 做出了許多調整,以使其在特定方案中更快速地執行。

ADO.NET

空位壓縮行支援:使用 SQL Server 2008 稀疏列功能的客戶經常會遇到空資料。 使用稀疏列功能的客戶可能會生成包含大量空列的結果集。 在此情況下,引入了行空位壓縮(SQLNBCROW 標記或者僅 NBCROW)。 此功能通過將具有 NULL 值的多個列壓縮為位掩碼,縮小了從具有大量列的伺服器傳送的結果集行所使用的空間。 這將極大地幫助依據表格格式資料流 (TDS) 協議來對包含眾多空列的資料進行壓縮。

Entity Framework

自動編譯 LINQ 查詢:如今在您編寫 LINQ to Entities 查詢時,Entity Framework 會遍歷 C#/Visual Basic 編譯器生成的表示式樹並將其轉換(或編譯)為 SQL,如圖 2 所示。


Figure 2 轉換為 SQL 的 LINQ to Entities 查詢

但是,將表示式樹編譯為 SQL 會產生一些開銷,尤其是對更復雜的查詢而言。 在早期版本的 Entity Framework 中,如果您希望避免每次執行 LINQ 查詢時必須為此效能損失承擔相應開銷,則必須使用 CompiledQuery 類。

此新版本的 Entity Framework 支援名為“自動編譯 LINQ 查詢”的新功能。 現在,您所執行的每個 LINQ to Entities 查詢均已自動編譯且位於 Entity Framework 查詢計劃快取中。 每次額外執行查詢時,Entity Framework 將在其查詢快取中查詢該查詢且不必再次完成整個編譯過程。

Windows Communication Foundation 和 Windows Workflow Foundation

Windows Communication Foundation (WCF) 和 Windows Workflow Foundation (WF) 團隊已完成此版本中的大量效能改進,例如:

  • TCP 啟用可擴充套件性改進: 客戶報告了一個 TCP 啟用問題,即,當有多個併發使用者使用固定重新連線傳送請求時,共享服務的 TCP 埠無法順利擴充套件。 此問題在 .NET 4.5 中已得到解決。
  • 對 WCF HTTP/TCP 的內建 GZip 壓縮支援: 使用這一新型壓縮功能,我們有望獲得高達 5 倍的壓縮比。
  • 在記憶體使用量較高時,回收 WCF 宿主: 當記憶體使用量較高時(可配置旋鈕),我們使用最近很少使用的 (LRU) 邏輯回收 WCF 服務。
  • 對 WCF 的 HTTP 非同步流支援: 我們在 .NET 4.5 中實現了此功能且獲得了與非同步流相同的吞吐量,但可伸縮性得到了增強。
  • 對 WCF TCP 進行了第 0 代碎片改進。
  • 針對大型物件優化了 WCF 的 BufferManager: 對於大型物件,已實現更好的緩衝池以避免產生更高的第 2 代 GC 成本。
  • 使用表示式快取改進了 WF 驗證: 對於載入並執行 WF 的核心方案,我們有望獲得高達 3 倍的改進。
  • 已實現 WCF/WF 端到端 Windows 事件跟蹤 (ETW): 雖然這並非效能改進功能,但它可幫助客戶進行效能調查。

ASP.NET

在共享託管已成為 .NET 4.5 的 ASP.NET 團隊的兩個主要效能目標的情形下,可改進站點密度(又定義為“每站點記憶體消耗”)和縮短站點的冷啟動時間。

在共享託管方案中,許多站點共享同一計算機。 在這種環境中,流量通常很少。 一些託管公司提供的資料顯示,多數情況下每秒的請求低於 1 rps,偶爾出現 2 rps 或更高的峰值。 這意味著許多工作程序在長時間(IIS 7 和更高版本中預設為 20 分鐘)空閒後可能會關閉。 因此啟動時間變得非常重要。 在 ASP.NET 中,這是網站接收並響應請求所需的時間,即工作程序關閉時與網站編譯完成時。

我們在此版本中實現了一些功能以縮短共享託管方案的啟動時間。 使用的功能包括:

  • Bin 程式集集中(共享公共程式集): ASP.NET 卷影複製功能允許在不解除安裝 AppDomain 的情況下更新該應用程式域中使用的程式集(因為 CLR 會鎖定正在使用的程式集,所以必須這樣做)。 通過將應用程式程式集複製到單獨位置(預設的 CLR 確定的位置或使用者指定的位置)並從該位置載入這些程式集,可實現此目的。 這將允許在卷影副本處於鎖定狀態時更新原始程式集。 預設情況下,ASP.NET 會為 Bin 資料夾程式集開啟此功能,以便能夠在站點啟動和執行時繼續更新 DLL。
  • 對於自定義 ASP.NET 控制元件、元件,或者需要在 ASP.NET 應用程式中引用以及跨站點中的各個頁面共享的其他程式碼的已編譯程式集 (DLL),ASP.NET 會將網站的 Bin 資料夾識別為一個特殊資料夾。 會在 Web 應用程式中的所有位置自動引用 Bin 資料夾中的已編譯程式集。 ASP.NET 還會檢測 Bin 資料夾中供網站使用的特定 DLL 的最新版本。 通常將旨在供 ASP.NET 站點使用的預先打包的應用程式安裝到 Bin 資料夾,而不是全域性程式集快取中。
  • ASP.NET 和 CLR 團隊已發現,當多個站點駐留在同一伺服器上且使用相同應用程式時,其中許多卷影副本 DLL 往往完全相同。 當從磁碟中讀取這些檔案並將其載入到記憶體中時,這將導致產生許多延長啟動時間和增加記憶體消耗的冗餘載入。 可通過對要遵循的 CLR 使用符號連結解決此問題,然後確定公共檔案並在特殊位置(符號連結將指向的位置)對其進行測試。 ASP.NET 將自動為要執行的 Bin DLL 配置卷影複製。 共享託管方現在可以根據 ASP.NET 指南設定其計算機,以便最大限度地獲得性能優勢。
  • 多核 JIT: 可參閱前面的“CLR”一節中的相關資訊。 ASP.NET 團隊使用多核 JIT 功能通過跨處理器核心擴充套件 JIT 編譯來縮短啟動時間。 此功能在 ASP.NET 中預設處於啟用狀態,因此您可以利用此功能,而無需執行任何其他操作。 您可以在 web.config 檔案中使用以下設定來禁用此功能:

          <configuration>
<!-- ...
          -->
<system.web>
<compilation profileGuidedOptimizations="None" />
<!-- ...
          -->
        
  • 預取器: Windows 中的預取器技術在降低應用程式啟動期間分頁讀取磁碟的成本方面非常有效。 現在,也在 Windows Server 上啟用了預取器(但並非預設情況)。 要為高密度 Web 託管啟用預取器,請在命令列中執行以下一組命令:

          sc config sysmain start=auto
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters" /v EnablePrefetcher /t REG_DWORD /d 2 /f
reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Prefetcher" /v MaxPrefetchFiles /t REG_DWORD /d 8192 /f
net start sysmain
        
  • 您稍後可以更新 web.config 檔案以在 ASP.NET 中使用它:

          <configuration>
<!-- ...
          -->
<system.web>
<compilation enablePrefetchOptimization
  ="true" />
<!-- ...
          -->
        
  • 為高密度 Web 託管優化 GC: GC 會影響站點的記憶體消耗,但可以對其進行調整以改善效能。 您可以優化或配置 GC 以提高 CPU 效能(降低迴收頻率)或降低記憶體消耗(即,更頻繁的回收以儘快釋放記憶體)。 若要啟用 GC 優化,可在 Windows\Microsoft\v4.0.30319 資料夾的 aspnet.config 檔案中選擇 HighDensityWebHosting 設定,以便每個站點消耗更少的記憶體(工作集):

          <configuration>
<!-- ...
          -->
<runtime>
<performanceScenario
  value="HighDensityWebHosting" />
  <!-- ...
          -->
        

可在 bit.ly/A66I7R 上的“ASP.NET 新版本入門”白皮書中找到有關 ASP.NET 效能改進的更多詳細資訊。

需要反饋

此處所列改進並不詳盡。 還有更多用於改進效能的微小更改,忽略這些更改是為了僅在本文中介紹主要功能。 除此之外,.NET Framework 效能團隊也始終致力於對 Windows 8 託管的 Metro 風格的應用程式進行效能改進。 在您下載並嘗試 .NET Framework 4.5 和 Visual Studio 11 Beta for Windows 8 後,如有關於未來版本的任何反饋或建議,請將您的想法告訴我們。

術語表

共享託管: 又稱為“共享 Web 託管”,高密度 Web 託管支援成百個(甚至上千個)網站在同一伺服器上執行。 通過共享硬體成本,可以更低的成本維護每個站點。 此技術大大降低了網站所有者的進入門檻。

冷啟動: 冷啟動是啟動尚未位於記憶體中的應用程式所需的時間。 您可以通過在系統重新啟動之後啟動應用程式來體驗冷啟動。 對於大型應用程式,冷啟動可能需要幾秒鐘,因為記憶體中不存在所需頁面(程式碼、靜態資料、登錄檔等),而將這些頁面載入到記憶體中需要進行費時的磁碟訪問。

熱啟動: 熱啟動是啟動已位於記憶體中的應用程式所需的時間。 例如,如果應用程式已在幾秒前啟動,則大多數頁面可能已載入到記憶體中且作業系統將重新使用這些頁面,這將節省較長的磁碟訪問時間。 這就是應用程式在您第二次執行它時啟動速度更快的原因(或者第二個 .NET 應用程式比第一個啟動更快的原因,因為 .NET 的部分內容已載入到記憶體中)。

生成本機映像(或 NGen): 可參考在執行之前將中間語言 (IL) 可執行檔案預編譯為計算機程式碼的過程。 這會產生兩個主要效能優勢。 首先,通過避免在執行時編譯程式碼,縮短了應用程式啟動時間。 其次,通過允許跨多個過程共享內碼表,改進了記憶體使用情況。 還有一個工具 NGen.exe,它可建立本機映像並將這些映像安裝到本地計算機上的本機映像快取 (NIC) 中。 當本機映像可用時,執行庫會載入這些映像。

配置檔案導引優化: 已證明配置檔案導引優化可縮短本機和託管應用程式的啟動和執行時間。 Windows 提供的工具集和基礎結構可對本機程式集執行配置檔案導引優化,而 CLR 提供的工具集和基礎結構可對託管程式集執行配置檔案導引優化,這稱為“託管配置檔案導引優化”(或 MPGO)。 Microsoft 中的許多團隊使用這些技術來改進其應用程式的效能。 例如,CLR 對本機程式集(C++ 配置檔案導引優化)和託管程式集(使用 MPGO)執行配置檔案導引優化。

垃圾回收器: .NET 執行時支援自動記憶體管理。 它會跟蹤託管程式進行的每次記憶體分配以及定期呼叫垃圾回收器,以查詢不再使用的記憶體並將其重新用於新的分配。 垃圾回收器執行的重要優化是,它不會每次都搜尋整個堆,而是將堆分為三代(第 0 代、第 1 代和第 2 代)。

壓縮: 在垃圾回收上下文中,當堆達到足夠零散的狀態時,垃圾回收器會通過移動活動物件以使其彼此靠近來壓縮堆。 壓縮堆的主要目的是使更大的記憶體塊可用於分配更多物件。

Ashwin Kamath 是 .NET 的 CLR 團隊的專案經理,負責提高 .NET Framework 4.5 的效能和改進可靠性功能。 他目前正在研究 Windows Phone 開發平臺的診斷功能。