ASP.NET Core 8 的記憶體佔用可以更低嗎?

張善友發表於2023-12-26

在 .NET 8 中,伺服器 GC 現在支援動態堆計數,它們新增了一個被稱為“動態適應應用程式大小”或 DATAS 的特性。它在 .NET 8 中通常是預設關閉的(儘管在為 Native AOT 釋出時預設開啟),但可以很容易地啟用,要麼透過將 DOTNET_GCDynamicAdaptationMode 環境變數設定為 1,要麼透過 <GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> MSBuild 屬性。所使用的演演算法能夠隨著時間的推移增加和減少堆計數,試圖最大化其對吞吐量的檢視,並在此和總體記憶體佔用之間保持平衡。

Maoni Stephens 是 .NET 垃圾回收器 (GC) 的首席架構師之一,她在2023年8月份發表了一篇關於 .NET GC 新功能的部落格文章,該功能稱為 Dynamic Adaption To Application Sizes (DATAS),該功能將隨 .NET 8 一起提供。此功能將在應用執行時自動增加或減少伺服器 GC 模式下的託管堆數量。它減少了 .NET 應用使用的記憶體總量,使伺服器 GC 模式成為記憶體受限環境(如 Docker 容器或 Kubernetes Pod)的可行選項,這些環境可以訪問多個邏輯 CPU 核心。

伺服器 GC 模式和工作站 GC 模式之間的差異

工作站模式最初是為客戶端應用程式設計的。過去,執行應用程式碼的執行緒會停止,直到 GC 執行完成。在桌面應用程式中,您不希望在幾毫秒甚至幾秒鐘內出現凍結,因此 Workstation GC 經過調整,可以更頻繁地執行執行,並更快地完成單個執行。從 .NET Framework 4.0 開始,我們還具有後臺 GC 執行模式,可最大程度地減少執行緒被阻塞的時間。

相比之下,伺服器 GC 旨在最大限度地提高服務的吞吐量,這些服務將隨著時間的推移接收短期請求。GC 執行頻率較低,但可能需要更長的時間。最後,您將在 GC 上執行上花費更少的時間,而將更多的時間花在服務程式碼上。

最明顯的區別如下:Workstation GC 僅使用單個託管堆。託管堆由以下子堆組成:

  • 小物件堆 (SOH) 及其三代 0、1 和 2。小於 85,000 位元組的物件將在此處分配。
  • 大型物件堆 (LOH),用於大於或等於 85,000 位元組的物件。
  • 固定物件堆 (POH),主要由為此執行互操作和固定緩衝區的庫使用(例如,用於網路或其他 I/O 方案)。

在伺服器 GC 模式下,您將擁有多個這樣的託管堆,預設情況下每個邏輯 CPU 核心一個,但這可以透過 GCHeapCount 進行調整。

託管堆數量增加,以及 GC 執行執行頻率較低,是解釋為什麼伺服器 GC 模式下記憶體消耗要高得多的重要因素。

但是,如果您希望從伺服器 GC 模式中受益,同時在執行時動態調整託管堆的數量,該怎麼辦?一個典型的方案是在雲中執行的服務,它必須在特定的突發時間處理大量請求,但之後它應該縮減以減少記憶體消耗。到目前為止,除了使用不同的配置值重新啟動服務外,您沒有辦法實現這一點。縱向擴充套件也需要重新啟動,因此許多開發團隊只是試圖透過 GCHeapCountConserveMemory 選項找到折衷方案。

這時,.NET 8 帶來了一項名為“動態適應應用程式大小”(DATAS) 的新功能就派上用場了。DATAS 在執行時將按以下方式執行:

  1. GC 將僅從單個託管堆開始。
  2. 根據稱為“吞吐量成本百分比”的指標,GC 將決定增加託管堆的數量是否可行。這將在每三次 GC 執行時進行評估。
  3. 還有一個稱為“空間成本”的指標,GC 使用它來決定是否應該減少託管堆的數量。
  4. 如果 GC 決定增加或減少託管堆的數量,它將阻塞您的執行緒(類似於壓縮 GC 執行)並建立或刪除託管堆。相應的記憶體區域將被移動。當涉及到託管堆中記憶體的內部組織時,在 .NET 6 和 .NET 7 中從段切換到區域,使此方案成為可能。

優點和缺點?

DATAS 允許在記憶體受限環境中使用伺服器 GC 模式,例如在 Docker 容器、Kubernetes Pod 。在您的服務將受到大量請求的攻擊突發期間,GC 將動態增加託管堆的數量,以便從伺服器 GC 的最佳化吞吐量設定中受益。突發結束後,GC 將再次減少託管堆的數量,從而減少應用使用的記憶體總量。即使在突發期間,GC 也可能選擇將託管堆增加到每個邏輯 CPU 核心少於 1 個,因此您最終可能會使用更少的記憶體,而無需手動配置託管堆的數量。

請記住:當應用只有一個邏輯 CPU 核心可用時,應始終使用 Workstation GC 模式。僅當應用有兩個或更多可用核心時,伺服器 GC 模式才有用。此外,我建議您驗證您是否確實需要伺服器 GC 模式。使用 K6NBomber 等工具來衡量 Web 應用的吞吐量。如果仔細設計了應用的記憶體使用情況,則吞吐量可能根本沒有差異。永遠記住:.NET GC 只會在分配記憶體時執行其執行。


DATAS 是一項很棒的新功能,它將 Workstation GC 和 Server GC 的優勢結合在一起:您開始時記憶體更少,當請求激增時,GC 可以動態擴充套件其託管堆的數量以提高吞吐量。當請求數在以後的某個時間點減少時,也可以減少託管堆的數量以釋放記憶體。

DATAS 可以在.NET 8 產品中使用,但是並沒有預設啟用,需要手動進行指定:

若要試用 DATAS,需要安裝 .NET 8 SDK,建立一個 .NET 8 應用(例如 ASP.NET Core),然後可以將以下兩行新增到 .csproj 檔案:

<PropertyGroup>
     <ServerGarbageCollection>true</ServerGarbageCollection>
     <GarbageCollectionAdapatationMode>1</GarbageCollectionAdapatationMode>
</PropertyGroup>

您還可以在構建專案時透過命令列引數指定它:

dotnet build /p:ServerGarbageCollection=true /p:GarbageCollectionAdapatationMode=1

或者在 runtimeconfig.json 中:

"configProperties": {
     "System.GC.Server": true,
     "System.GC.DynamicAdaptationMode": 1
}

或者透過環境變數:

set DOTNET_gcServer=1
set DOTNET_GCDynamicAdaptationMode=1

請記住:使用上述方法之一時,不得設定 GCHeapCount 選項。如果這樣做,GC 將只使用指定數量的堆,而不會啟用 DATAS。同樣重要的是:如果要在工作站模式下執行,只需將 ServerGarbageCollection 或相應的配置屬性/環境變數分別設定為 false 或零。

預設情況下,我的 ASP.NET Core 應用將使用哪種 GC 模式?

你的 ASP.NET Core 應用可以訪問多少個邏輯 CPU 核心?如果小於兩個,則將使用 Workstation GC 模式。否則,預設情況下將啟用伺服器 GC 模式。因此,在 Docker、Kubernetes 或雲環境中為應用指定約束時要特別小心,因為這些環境可能會突然進入另一個 GC 模式,佔用的記憶體比預期的要多。NativeAOT 的應用程式由於預設啟用了新的 DATAS GC 模式,使用本機 AOT 釋出的 ASP.NET Core 應用可以減少記憶體需求。這種記憶體消耗的減少有助於提高部署密度和提高可伸縮性。

相關文章