由於等待應用程序啟動是令許多用戶都感到沮喪的一件事情,因此,側重於提高客戶端應用程序的啟 動性能將極大增強客戶的第一印象,並使他們對您的努力成果印象深刻。同時,鑒於啟動性能對用戶非常 重要,所以值得研究一下其影響因素,這樣才能避免最常見的錯誤。
應用程序啟動通常分為冷啟動和熱啟動。在托管應用程序環境中,冷啟動是指 Microsoft® .NET Framework 系統程序集和應用程序代碼均不在內存中時,因而需要從磁盤提取它們。熱啟動則是指應用程 序的後續啟動,或者當大部分系統代碼因之前由另一托管應用程序使用而已經存在於內存中時的應用程序 啟動。
冷啟動
在大多數情況下,冷啟動受 I/O 限制。換句話說,等待數據花費的時間長 於處理指令所用的時間。啟動應用程序所用時間等於操作系統從磁盤提取代碼所用時間與執行其他處理( 如對 IL 代碼執行 JIT),以及在應用程序啟動路徑中執行的任何其他初始化所用時間的總和。由於處理 通常並不是冷啟動的瓶頸,因此,所有應用程序啟動性能調查的初始目標都是通過降低加載的代碼量來減 少磁盤訪問。
寫入應用程序代碼的方法對冷啟動也有著重要影響,所以弄清楚下列情況非常重要 ,如啟動時應用程序是否打開其他文件或者啟動可能會爭奪 I/O 資源的其他進程。
由於冷啟動是 受 I/O 限制的情形,因此使用傳統的 CPU 分析器(無論是基於檢測還是基於采樣)對調查並無顯著幫助 。基於檢測的分析器將等待 I/O 所用的時間報告為阻塞時間。問題是,即使您能夠將阻塞時間歸因於某 個特定調用堆棧,阻塞時間也僅計一次。之後的所有磁盤 I/O 都會被忽略,從而導致將磁盤 I/O 的部分 實際時間當成了總執行時間。
使用基於采樣的分析器時,所收集信息甚至可能會產生誤導。它跟 蹤的是 CPU 使用率而非 I/O,因此,在分析器的報告中不會記錄 I/O 所用的總時間。
可通過圖 1 切身體會一下冷啟動由於連續兩次啟動應用程序而受 I/O 限制的情況。第一次啟動很可能比第二次啟 動慢很多(在第二次啟動時,由於第一次啟動的原因,執行所需的大部分代碼均已存在於內存中,因而避 免了磁盤訪問並節省了時間)。當然,要確保第一次啟動是真正的冷啟動,首先需要重新啟動計算機,並 確保在用戶登錄時啟動文件夾中無托管應用程序,並且沒有使用托管代碼的 Windows® 服務在運行。
Figure 1 冷啟動中的磁盤讀取時間和 CPU 時間
請注意,要執行理想的冷啟動測試,應禁用 SuperFetch 服務,否則它可能會預加載一些應用程序所 需的代碼,從而造成更熱的啟動情形。在關閉 SuperFetch 的情況下進行測量的好處是:可確定應用程序 所需的全部代碼均是在應用程序啟動時才載入內存,因而可更加准確地衡量 I/O 的成本。然而,應記住 的一點是:您所衡量的並不一定就是實際的用戶體驗,因此切勿根據關閉 SuperFetch 時收集的數據對應 用程序的實際性能作出任何具體結論。
可使用以下兩個性能計數器來了解冷啟動對 I/O 的影響: 處理器時間百分比和磁盤讀取時間百分比。如果 I/O 制約冷啟動(事實應該就是如此),您會發現處理 器時間百分比和磁盤讀取時間百分比之間存在著很大差異。可使用 PerfMon 來收集性能計數器(有關詳 細信息,請參閱“啟動性能資源”側欄)。
在圖 1 中,紅線代表磁盤讀取時間百分比 ,綠線代表處理器時間百分比。在冷啟動的情況下,您會發現與從磁盤讀取所用時間相比,CPU 使用率相 對較低。
第二次啟動應用程序時則為熱啟動情形,因此性能計數器會顯示另一幅圖片。圖 2 為受 CPU 限制的情形,如您所見,與處理器時間百分比相比,磁盤讀取時間百分比非常低。
Figure 2 熱啟動時間更短
熱啟動受 CPU 限制,因為代碼已存在於內存中,所以無需其他 I/O;但在運行應用程序之前需先對代 碼執行 JIT。現在借助 .NET Framework,JIT 所生成的本機代碼不會在每次執行應用程序時都保存。
如果您發現熱啟動時間並未明顯短於冷啟動,則需弄清楚是什麼正在占用 CPU 周期(因為熱啟動 時已預加載了大部分代碼,因此不可能受 I/O 限制)。可能的原因包括必須對大量代碼執行 JIT,或者 應用程序必須執行非常復雜的計算。
要確定 JIT 執行是否為問題所在,可檢查 JIT 中性能計數 器 .NET CLR JIT 的時間百分比。如果值不高(例如,對於大部分啟動時間,超過 30%-40%),則意味著 JIT 不太可能是主要因素,應使用分析器來確定應用程序中的哪些功能占用了大部分 CPU 時間。請記住 ,僅當對方法實際執行 JIT 時,計數器才會更新。這意味著在對最後一個方法執行 JIT 後,計數器仍會 報告上一個值;它不會降為零。因此,請確保僅在應用程序啟動時的前幾秒查看該計數器;此時,您可能 會發現計數器數值增加的非常快,這表明 CPU 使用率峰值是由 JIT 編譯器所致。
另請注意,用 戶登錄時加載的所有應用程序一定會與其他服務和應用程序一起爭奪 I/O,從而導致啟動時間更長。因此 ,應盡量避免向啟動組添加應用程序(可利用 AutoRuns 這個不錯的工具來確定將哪些應用程序設置為在 計算機啟動時運行,該工具可從 microsoft.com/technet/sysinternals/Security/Autoruns.mspx 處獲 得)。
確定從磁盤加載的代碼
下一步是確定從磁盤加載的內容並弄清楚是否有無意間加載的代碼。確定已載入內存的內容的最快捷 方法是使用 VADump 工具(可在 Windows Platform SDK 中找到)。圖 3 顯示了通過運行以下命令生成 報告的摘錄:
Figure 3 VADump Output
Category Total Private Shareable Shared Pages KBytes KBytes KBytes KBytes Page Table Pages 177 708 708 0 0 Other System 39 156 156 0 0 Code/StaticData 8169 32676 2160 8336 22180 Heap 14042 56168 56168 0 0 Stack 0 0 0 0 0 Teb 0 0 0 0 0 Mapped Data 8 32 0 4 28 Other Data 1 4 4 0 0 Total Modules 8169 32676 2160 8336 22180 Total Dynamic Data 14051 56204 56172 4 28 Total System 216 864 864 0 0 Grand Total Working Set 22436 89744 59196 8340 22208 Module Working Set Contributions in pages Total Private Shareable Shared Module 72 2 70 0 HeadTrax - HeadTrax.exe 107 7 0 100 ntdll.dll 37 4 6 27 mscoree.dll 77 3 0 74 KERNEL32.dll 6 2 0 4 LPK.DLL 27 4 0 23 USP10.dll 116 4 0 112 comctl32.dll 878 23 79 776 mscorwks.dll Heap Working Set Contributions 0 pages from Process Heap (class 0x00000000) 0 pages from Process Heap (class 0x00000000) 9332 pages from Process Heap (class 0x00000000) 0x0255850F - 0xC255350F 9332 pages 0 pages from Process Heap (class 0x00000000) 0 pages from Process Heap (class 0x00000000) 4710 pages from Process Heap (class 0x00000000) 0x00040000 - 0x10040000 4710 pages 0 pages from Process Heap (class 0x00000000) Stack Working Set Contributions 0 pages from stack for thread 00001018 0 pages from stack for thread 000017EC 0 pages from stack for thread 0000187C VADump –sop <proc ID>
需要記住的重要一點是:VADump 僅顯示在運行此工具時 加載到內存中的內容,因此,它可能會漏掉僅在內存中加載很短時間的模塊。它也不會顯示已分頁輸出到 磁盤的應用程序部分(代碼或數據)。因此,目標是查看 VADump 報告以確定是否有必要加載列表中的所 有模塊。例如,如果您的應用程序並未使用 XML 而您發現加載了 System.Xml,則有必要調查一下原因。
可通過在 Windows 調試器 (windbg) 中運行 sxe 命令來確定加載程序集的對象。“sxe ld:<dll name> ”命令將導致調試器在加載指定的 DLL 時中斷。之後,可檢查調用堆棧以確定哪一個功能導致 DLL 被加 載到內存中。不應低估此方面的調查。應用程序實際加載到內存中的內容極易被忽視。
系統程序集和其他進程
解決了在啟動時加載所有不必要的程序集這一問題後(如果需要進一步改進,還可以修改應用程序代 碼以延遲啟動時執行的某些初始化工作),下一步就是減少從系統程序集加載的代碼量。不幸的是,據我 所知,如果使用了系統 API,則沒有工具可以確定所提取的代碼量。如果存在此類工具則會非常有用,因 為開發人員可以在啟動代碼中使用需要從系統程序集加載更少代碼的 API。在出現此類工具後,您就可以 利用基於檢測的分析器(如 Visual Studio 性能工具)來了解某個 API 的大致頁面成本。
通過查看分析數據,您可以盡量避免涉及那些具有大型調用樹(大型樹的深嵌套調用意味著每個方法 調用的代碼均從磁盤提取—因此,這是一種代價高昂的調用)的系統調用的 API。如果可以通過調用無深 系統調用樹的 API 來實現相同的功能,則會節省時間。這並非是一個科學的方法,因為它很難確定去掉 一個調用樹可節省多少代碼,但通常情況下會具有一定意義,畢竟調用樹越大,需要從磁盤加載的代碼量 就越大。
在某些情況下,您的應用程序可能在啟動時顯式或隱式啟動其他進程。可通過在 Windows 調試器 (windbg) 中使用 –o 選項來輕松地確定它們是什麼進程。–o 選項會使調試器附加到任何子進程。應用 程序隱式啟動進程的典型示例發生在應用程序使用 XML 序列化且未預編譯序列化類時(使用 Sgen 實用 程序)。此時,將啟動 C# 編譯器來編譯它們。啟動其他進程通常是一個代價高昂的操作,會對啟動造成 重大影響。
NGen 性能
本機映像生成 (NGen) 始終有助於改善熱啟動,因為它可以避免對代碼執行 JIT。如果無需加載 mscorjit.dll,則 NGen 對冷啟動情形也會有所幫助,因為應用程序所用的全部代碼已使用 NGen 進行了 預編譯。但是,如果只有一個模塊沒有對應的本機映像,則仍會加載 mscorjit.dll。然後,不僅會對代 碼執行 JIT(因而占用 CPU 周期),而且還會由於 JIT 編譯器需要讀取元數據而接觸到 NGen 映像中的 許多頁面。這將導致啟動更糟。為此,建議刪除可能導致在啟動期間執行 JIT 的所有代碼。當然,是否 應采用此方法只能在使用和不使用生成的本機映像測量冷啟動性能後再做決定,因為 NGen 對冷啟動的實 際優勢取決於應用程序代碼和大小,因此即使在啟動時並無 JIT 操作,也無法保證會有重大的啟動改進 。 確定是否以及何時執行 JIT 的一個方法是使用托管調試助理 (MDA)。JIT MDA 允許您在對某方法執行
JIT 時進入調試器或打印調試信息。可通過設置環境變量來啟用 MDA,如下所示:
COMPLUS_MDA=JitCompilationStart
當對代碼執行 JIT 時,應用程序會進入調試器。也可使用注冊表或應用程序的 .config 文件來設置 MDA。有關如何使用 MDA 的詳細信息,請參閱“啟動性能資源”側欄。
通常情況下,為保證 NGen 有利於冷啟動性能,請確保:
整個應用程序已進行了 NGen。
無基類重定位。基類重定位是一個代價高昂的操作,而且重定位基類的代碼無法共享。可在 msdn.microsoft.com/msdnmag/issues/06/05/CLRInsideOut 中找到如何設置基址的更多詳細信息。 程序集安裝在全局程序集緩存 (GAC) 中。強名稱驗證需要接觸整個文件,但它會跳過在 GAC 中安裝的 所有程序集。
Authenticode 驗證
可使用 signcode 工具對程序集執行 authenticode 簽名。Authenticode 驗證對啟動始終具有負面影 響,因為 authenticode 簽名後的程序集需要由證書頒發機構 (CA) 來驗證。此驗證需要校驗用於簽名程 序集的 CA,而這一操作由於需要訪問網絡(如果未將 CA 本地安裝在同一計算機上)而代價高昂。 理想情況下,應避免對程序集進行 authenticode 簽名,請改為使用強名稱簽名。如果無法避免 authenticode 簽名,可在 .NET Framework 3.5 中使用以下配置選項來跳過驗證:
<configuration>
<runtime>
<generatePublisherEvidence enabled="false"/>
</runtime>
</configuration>
但是,請注意:在必須進行 authenticode 簽名時,仍可節省驗證所需的大部分時間,方法是在客戶 端計算機上安裝 CA 證書。
結束語
為獲得良好的冷啟動性能,最佳做法是讓啟動時執行的代碼極其精簡。這意味著延遲並非必不可少的 初始化、檢查所有引用以確保其加載速度不是過快,並盡量使用無需加載大量代碼的類和方法。請記住, 目標是減少磁盤訪問。這並非易事,但即將發布的 Windows Server 2008 SDK 中包含一個非常有用的新 工具 Xperf,它使用 Windows 事件跟蹤 (ETW) 來跟蹤加載的模塊、上下文切換和其他事件,從而有助於 確定應用程序啟動期間所發生的操作。使用 Xperf,您將可以收集有關應用程序啟動時間的非常精確的指 標。“啟動性能資源”側欄中包含許多非常有用的詳細參考。
請將您想詢問的問題和提出的意見發送至 [email protected].