本文章是由機器翻譯。

深究 CLR

稽核對.NET 應用程式的記憶體使用量

Subramanian Ramaswamy and Vance Morrison

內容

當記憶體使用量會影響速度
可以無法完成哪些?
[工作管理員]
共用與共用記憶體
應用程式的大小
VADump: A 更詳細的檢視
.NET 的記憶體回收行程
PerfMon
結論

效能最佳化是關於一的東西: 讓電腦執行速度更快的程式。昂貴指令的運算元的擷取時,則執行的指令都便宜的現代電腦硬體。因此,記憶體使用量可能會造成直接影響,如何快速應用程式上執行,並會以最佳化的重要度量資訊。在這的篇文章中,我們討論.NET 程式的記憶體最佳化的基本概念。前,我們會概述在記憶體存取的瓶頸和可以最佳化的情況。接下來,我們討論了一般的分析,如何在一般的.NET 程式中使用記憶體。最後,我們討論的工具和策略,判斷.NET 應用程式的記憶體耗用,並減少它。

當記憶體使用量會影響速度

第一種情況的記憶體耗用量的重要時將是您,需要大量 CPU 的應用程式,管理大量的資料。一般 PC 可以執行指令在一個少於半奈秒為 (0.5 來)。不過的速度會受到花費,從記憶體中擷取運算元。現代的處理器會有快取的一個階層架構,以最佳化的硬體成本。層級 1 (L1 快取是在最快的記憶體,但相對較小。接下來,在階層架構中是後面再加上主記憶 (RAM) 和最後硬碟機層級 2 快取。[圖 1 ] 顯示存取時間和一般的 PC 的記憶體階層架構的各部分的大小。在每個步驟更深入到記憶體的階層架構,存取時間 (和大小) 增加的順序或多個 (10,000 個的時間低於 RAM 是硬碟機) 時,(每個位元組) 的成本降低。

[圖 1 的大小與存取的時間與非本機存放裝置
  L1 快取 L2 快取 記憶體 (RAM) 磁碟
大小 64K 512K 2000M 100,000M
存取時間 .4 ns 4 來 40 ns 4,000,000 ns

如果作用中資料的路徑,存取更多的記憶體,則運算元經常需要擷取從較慢的記憶體。因為較慢的記憶體是較慢,所執行的順序,幾個層級 2 快取遺漏可能造成顯著的效能影響。

應用程式的冷啟動期間,是第二種情況的 (有些) 記憶體耗用量的重要時。[圖 1 ] 顯示,存取硬碟的磁碟是比主要記憶體存取更慢。作業系統會嘗試緩和這由快取資料從在主要記憶體磁碟。這是應用程式是速度更快啟動第二次期間呼叫 (資料在更快的記憶體中快取) 的暖啟動時使用的原因。在第一個 (冷啟動快取不尚未發生並具有從磁碟擷取資料。只改善這就是從磁碟中載入較少的資料。只能從的磁碟 (例如,程式的指示) 讀取的記憶體影響初次啟動,; 程式本身,包括堆積和堆疊上的所有資料來初始化的記憶體不會影響初次啟動]。

在應用程式切換記憶體耗用問題時,最後一個情況。您的應用程式時相當大 (大於 50MB),使用者切換至其他應用程式,這些應用程式會竊取您的應用程式的實體記憶體。當使用者傳回至您的應用程式時,這些遭竊的頁面需要擷取從磁碟讓您的應用程式非常緩慢。這與很冷啟動的情況,但它會影響不只是程式指示,但是所有的記憶體類似 — 包括您的應用程式初始化的記憶體。由於伺服器會執行許多不相關的程式,同時並持續,伺服器不斷切換的應用程式 ; 這表示記憶體幾乎都是一個的問題,伺服器。

可以無法完成哪些?

如果重新程式碼無法被魔法給封住了排列以確保所有記憶體要求已滿足 「 快速快取中的,程式可以大幅加快。在練習,這是在罕見的情況下只可能,因為一般程式的演算法指示記憶體存取的順序。在更適合的技術是最小化的記憶體使用量。這會減少快速的快取的負載,並讓程式的速度更快。資料的結構有經常存取 (快速) 組件的 CPU 快取不符合該執行 (通常它們會是數個 MB 以上),30 %減少記憶體大小的作用中資料通常會導致 CPU 速度為 10 %改進。

有三種方式,可減少記憶體。先,您可以執行較少的程式碼 (這可協助冷啟動)。這適用於明顯的情況下,內容已計算 inefficiently 的。第二個,您可以觸控較少的資料。這是類似於 「 第一個的策略,但它套用至相關的資料結構中。最後 (和可能是最常),資料結構可以編碼以不同的方式讓它縮小,或實際分隔 (小型) 經常存取資料從 (大型字) uncommonly 存取的一部份。

這些技巧通常會需要在表示中資料的變更,並需要大量的程式碼的站台實作的變更。因此,它就容易得多進行這些變更早期會在開發週期,因此值得考慮記憶體 (早期 !

[工作管理員]

第一個的步驟,在減少您的應用程式在記憶體消耗量是瞭解有多少它正使用。,您可以使用 「 Windows 內建的 [工作管理員] 應用程式。

大部分的使用者已熟悉使用 [工作管理員]。您可以它輸入 taskmgr 在您執行的命令視窗 (Winkey + R) 中或叫用按 Ctrl + Alt + Del,選取 [啟動工作管理員 」。在處理程序 >,索引標籤上,您會找到所有系統的目前執行的處理序的資訊。如果資料行不包含 PID,記憶體-工作設定] 和 [記憶體-私人工作集,使用檢視 | 選取將它們加入至顯示的資料行的功能表選項。

共用與共用記憶體

工作集將是您,目前處理序所使用的實體記憶體。不過,作業系統會執行最佳化,以確保所有的記憶體不是同樣昂貴。大部分的處理序所使用,記憶體保留唯讀資料 (例如,實際指示來執行)。因為這個資料是唯讀它可以在所有需要的處理程序之間共用。因為所有處理程序會發出大量使用共用的、 唯讀的作業系統程式碼,被共用每個處理序的工作集在大量量。因此,總數的工作集,會大幅 overestimate true 程序所使用的記憶體的成本。

作業系統也會追蹤的非共用的 (Private) 記憶體。這包括處理序所使用的所有讀取與寫入記憶體。私人工作組會低估 true 的成本,處理序所使用的記憶體時 (我們將會看到如何時我們會討論工具 VADump),它會是最佳化,更好的度量單位,因為不同於最佳化共用的記憶體,私密的記憶體中的任何提升會減少在電腦上的總記憶體壓力。

最後,同時總和私密記憶體計數會遺漏的處理序所使用的重要記憶體: 檔案系統快取。因為硬碟存取是因此昂貴,甚至時為檔案的資料未對應直接將記憶體,它是快取由作業系統。這個記憶體的使用會增加的系統記憶體壓力,,不會包含在其中一項工作設定度量資訊 (它擁有由作業系統)。沒有可以完成檔案存取的大部分 (如果您的程式需要的檔案,它無法避免),因此它可以被視為無法最佳化的成本。

應用程式的大小

應用程式可能被分類為小]、 [媒體,或 [大,視其記憶體使用量中。小型的應用程式有 20MB 或較小的工作設定的較小的大小比 5MB 私人工作組,一個中度的應用程式使用約有 20 MB 私人工作組中設定大小大約 50MB 的工作,大型的應用程式通常有工作集大小大於 100 MB,使用私密的工作集大小超過 50MB。較大的您的應用程式更多的珍貴最佳化應用程式的記憶體使用量很可能。

監視記憶體使用量和遺漏的核取,是一個簡單且快速的方法是執行探查測試您的應用程式中。執行應用程式時,並監視其工作集的使用方式 ; 如果未繫結的工作集增加,可以表示記憶體遺漏 (Memory Leak) 或其他問題就可以了。

VADump: A 更詳細的檢視

[工作管理員] 會提供只應用程式之記憶體使用量的摘要。若要取得更詳細的說明必須名 VADump 工具 (請參閱資源資訊看板)。這會叫用 VADump) 在安裝的目錄下命令提示字元中輸入 VADump –sop ProcessID。它會列印到 DLL 的細微的層次單一處理序中的記憶體的分析。一般的傾印如 [圖 2 ] 所示。

fig02.gif

[圖 2] 在 [記事本] 中,開啟 VADump 輸出

要讀取傾印,開頭總計總數的工作集。這個數字應該同意數目在 [工作管理員] 中。這個數字會再分成八個類別。這些類別的最有趣的是:

  • 程式碼 / 靜態資料,表示由處理序所載入的 DLL。
  • 堆積的表示原生 (不是 GC) 堆積記憶體。
  • 其他資料,代表使用作業系統 VirtualAlloc 函式配置的記憶體。Managed 程式碼這很重要,因為它包含整個記憶體回收堆積。

效能的資源

VA 傾印:

go.microsoft.com/fwlink/?LinkId=149683

CLR 效能小組部落格 (指示調查可疑的 DLL 載入):

blogs.msdn.com/clrperfblog

VS 的程式碼剖析工具小組部落格:

blogs.msdn.com/Profiler

改善.NET 應用程式效能] 和 [延展性:

msdn.microsoft.com/library/ms998530

Windows 效能的部落格: 使用 Xperf 的調查:

blogs.msdn.com/pigscanfly/

Vance Morrison 的部落格:

blogs.msdn.com/vancem

波多黎各 Mariani 部落格:

blogs.msdn.com/ricom

Lutz Roeder 的.NET 反射程式來檢查程式碼中:

blog.lutzroeder.com

深究 CLR-勘查記憶體問題:

msdn.microsoft.com/Magazine/cc163528

使用 DLL 在記憶體是進一步細分 VADump 所摘要的資料表之後。每個的 DLL 它顯示數目 (頁面是永遠 4K) 的頁面的每一個 DLL 使用。因此,一個可以決定會載入的所有程式碼的記憶體成本。

[圖 2 中,,沒有資料列標示為 「 設定大法總數的工作 」。 總工作集以 KB 為單位) 和頁面會位於第一個資料行中。2、 3 和 4 (私密 KB、 可共用 KB 與 SharedKBytes) 的資料行加入設定工作集的資料行的總計。它是資料行 2,私密的 KB 值,而資料行 1 顯示為總工作集 TaskManager 描繪 TaskManager,在私密的工作設定。因此,VADump 可讓您看到私密和總數的工作集,包括可共用和共用的工作集之間的分隔。這是一個更完整的圖片,比可用透過 TaskManager。

.NET 應用程式大時它們是通常是大型可能是因為它們執行大量的程式碼,或在使用大量資料。

在這種情況下將會瞭解大量載入的 DLL 的您],以及程式碼 / 靜態資料比重會支配總工作集。Managed 應用程式,此資料會在 GC 堆積,,因此顯示 dominating 工作集的其他資料。

[圖 2 部分較低中, 您會看到工作列的集合 (以頁顯示) 的模組。這會告訴您哪個模組提供應用程式和多少工作設定每個模組的工作集使用。因此,您可以非常快速地決定多少特定 DLL 的 DLL 的私用、 共用,和可共用工作集所提供的工作集。此檢視會明確顯示是否可以被排除 DLL 載入和多少位元組私人工作組可以 shaved 關閉應用程式的工作集。

一次可能無法付費播放已識別的 DLL (例如,DLL 可能會載入即使不使用特定的執行中),下一個步驟是識別特定 DLL 取得載入的原因,並將想要排除的不必要的負載。步驟的調查可疑的 DLL 載入可以在中找到,CLR 和 Framework 效能部落格.

堆積資料 VADump 輸出所顯示的是在 Unmanaged 堆積的 — 這是記憶體,無法將由.NET GC 進行管理。您最好保留這個數字小,因此 GC 可以管理您的記憶體的大部分必要時清除。

其他的資料類別,表示函一個基本作業系統記憶體配置式的呼叫 (VirtualAlloc) 的 VADump 無法分類的任何其他的方式。.NET 應用程式,通常是最重要元件的其他資料是保留所有的使用者定義物件回收的堆積。

.NET 的記憶體回收行程

.NET 執行階段會支援自動記憶體管理。它會追蹤 Managed 的程式所做的每個記憶體配置,並定期呼叫 GC 找到不再使用,且它重複使用的新配置的記憶體。重要的最佳化記憶體回收行程執行會是它不會每次,搜尋整個堆積,但分割為 (0、 1 和 2) 的三個層代的堆積。

層代 0 最小的且通常會只使用 1 / 第十一個的毫秒數來完成,但只看起來,以清除配置之後,最後一個 GC 發生 (或很明顯地,未使用)。在理想的情況下,在產生的大小小於 L2 快取大小。第 1 個層代 GC 會處理利用未被一個 GC 使用的配置,較長執行比 Gen 0 花大約 1 毫秒的 GC。在理想的情況下,應該有 10 個 Gen 0 GC,每個 Gen 1 的 GC。

Gen 2 GC 會處理所有的物件。因此,花費的時間可以是重要。例如,需要約為 160 個毫秒 20MB 堆積是一個值得注意的時間。時間隨著大約用線性方式在堆積 (大約 8 MB,為非常粗略的估計每毫秒) 的大小。為 true 的成本取決於剩下,GC 的指標,在剩下的記憶體,且分散堆積 (Heap) 的數目的記憶體量。在理想的情況下,應該有 10 個 Gen 1 GC,為每個 Gen 2 GC。

採取的整體,.NET GC 堆積起來一個 sawtooth 與對應至 Gen 2 的集合,如 [圖 3 ] 所示,troughs。一般 Gen 2 堆積 trough 比例將為您,1.大約 6,比例的堆積的大小 (以未分散) 主要是獨立的。分散程度的這個數字可大幅改變。

fig03.gif

[圖 3 GC 堆積 Sawtooth Waveform

效能

VADump 會提供第一層的處理序中的記憶體使用量的分析。不過,它無法精確地告訴我們我們使用多少 GC 記憶體 (其他的資料類別可以包含其他比在 GC 堆積記憶體,),它不會不告訴我們是否 [狀況良好] GC 的層代的比率。我們需要使用 Windows PerfMon 應用程式的。您可以鍵入 [執行] 命令視窗如 [圖 4 ] 所示的視窗應顯示的 PerfMon 來啟動它。PerfMon 可收集豐富的效能的資料,但此處我們著重在監視在 GC 堆積的使用。

[圖 4 PerfMon 啟始畫面

fig05.gif

[圖 5 選取以監視在 PerfMon 計數器

PerfMon 出現之後,我們要將它設定為顯示 GC 的相關資訊。我們這麼第一次按一下在左邊窗格中樹狀目錄控制項中的效能監視器項目。這會變更右窗格以顯示效能計數器資料。現在,請按一下 + 註冊加入新的計數器。接著,請選取您要監視,以及您想要監看,如 [圖 5 ] 所示的處理程序計數的器。

當您選取一些計數器時,您會發現使用執行階段的所有應用程式的名稱。您可能會選取您要監視的一或兩個或不過許多應用程式。此外,沒有名為所有的執行個體執行個體就是在顯示的所有執行個體上啟用監視資料,但是會分別顯示資料。到目前此外,有是不同的執行個體中的資料加總一 _Global_ 執行個體。

如果 PerfMon 正在使用已監視其他應用程式之後,已啟動的應用程式,一個可以按一下來新增更多的應用程式,+ 簽署,並加入新的應用程式的計數器 (只加入新的執行個體是必要的 ; 其他執行個體將繼續在 PerfMon 中顯示)。

最後,依預設值以圖形方式,顯示資料,但很更有用,以顯示數值。這可以是藉由按一下 [報告類型] 工具列 ( 見 [圖 6 ) 上完成的。在一個測試,它顯示我們出 8.6MB 私人工作集的會是 GC 堆積所佔用,並在 GC 中花費大約百分之 11 時間總計的該 7.3MB。Time in GC 一個 [狀況良好] 號碼不小於 10 %總數的應用程式的這個特定的應用程式上,borderline 因此時間。最後,它也告訴我們號碼的 Gen0、 Gen1 及 Gen2 的集合。在理想的情況下,我們希望的 Gen1 的集合是至少 10 次的 Gen0 回收數目] 和 [Gen1 回收是至少 10 次 Gen2 集合的數目。

fig06.gif

[圖 6 PerfMon 報表顯示

結論

可能難以偵錯是記憶體問題的。如果您的應用程式夠大,以關心記憶體,金鑰是在開發的早期階段中限制記憶體使用量。瞭解如何關閉應用程式的記憶體已損毀是在此程序中第一個步驟,; 監視應用程式的記憶體使用量是下一個]。請遵循,以識別的機會,所以要求有關哪些 DLL 提供最多的記憶體耗用量為什麼 Gen2 GC 會因此經常發生,您的記憶體配置付費播放,等的問題。然後適當地最佳化記憶體使用量。如果您立即採取只有一個單元,它應該是時間花在開發週期的記憶體問題的早期這麼本身的更新版本,因此真的值得早期考慮記憶體!

您問題或意見寄至clrinout@Microsoft.com.

Subramanian Ramaswamy 會是在 Microsoft 的 CLR 效能的 [程式] 經理。他存留在的博士學位,在 [電子和電腦從在喬治亞州的工程制定的技術。

Vance Morrison 是協力廠商的設計師在 Microsoft 的 CLR 效能群組管理員]。他導引.NET 中繼語言 (IL) 的設計,他一直與.NET 相關從其開始。