該頁面可以包含自動翻譯的文字。
C# Native AOT 效能
與常規託管程式碼相比,.NET Native AOT 應用程式的速度有多快? AOT 能超越 JIT 嗎?如何對 Native AOT 應用程式進行基準測試?
本文是 .NET 中 Native AOT 的系列文章的一部分。如果您不熟悉 Native AOT,請先閱讀 如何在.NET中開發Native AOT應用程式 部分。
本文比較 .NET 和 Native AOT 效能。首先,我們將回顧一下微軟官方基準測試。它們允許比較簡單 ASP.NET 應用程式的不同 .NET 部署選項。
然後,您將學習如何使用 BenchmarkDotNet 和 hyperfine 工具來執行自己的基準測試。此類基準測試可讓您測量環境中的程式碼速度。
ASP.NET 基準測試
ASP.NET 團隊維護可靠的效能測試基礎架構。他們在不同的環境中測試各種場景。
我們對 Native AOT 基準最感興趣。資訊的主要來源是以下PowerBI儀表板。那裡的數據基於 3 個「鯨魚」:測試應用程式、部署場景和指標。
測試應用
您可以在 aspnet/Benchmarks 儲存庫中找到基準測試和測試應用程式的原始程式碼。
Native AOT 基準測試比較 3 種應用程式類型:
- Stage1 - 基於 HTTP 和 JSON 的最小 API。應用程式原始碼位於“/src/BenchmarksApps/BasicMinimalApi”。
- Stage1Grpc - 基於 gRPC 的類似 API (
/src/BenchmarksApps/Grpc/BasicGrpc
) - Stage2 - 涉及資料庫、身份驗證的完整 Web 應用程式 (
/src/BenchmarksApps/TodosApi
)
.NET 部署場景
測試應用程式在不同的環境中運行。目前,基準測試使用 28 核心的 Windows 和 Linux 虛擬機器。 ARM 和 Intel 處理器也有單獨的 Linux 環境。
應用程式也在不同的配置下進行了測試。某些配置中的應用程式定義了「場景」。
您可以按住 Ctrl(或 ⌘)鍵在 PowerBI 儀表板上選擇多個方案或環境。
指標
基準收集每個已部署應用程式的基本指標。例如,測試測量每秒請求數 (RPS)、啟動時間、最大記憶體工作集。
這使我們能夠比較同一應用程式的各種配置的指標值。
性能對比
我們將比較 StageX 場景與 StageXAot 和 StageXAotSpeedOpt。他們使用以下配置:
設想 | dotnet publish 建置參數 |
---|---|
StageX | PublishAot=false EnableRequestDelegateGenerator=false |
Stage2Aot | PublishAot=true StripSymbols=true |
Stage2AotSpeedOpt | PublishAot=true StripSymbols=true OptimizationPreference=Speed |
上述所有場景也使用DOTNET_GCDynamicAdaptationMode=1
環境變數。
StageXAotSpeedOpt 場景允許估計 OptimizationPreference = Speed 設定的影響。
您也可以查看 StageXTrimR2RSingleFile 場景。此類場景對應於修剪後的 ReadyToRun 部署,這是 .NET 中提前編譯的另一種形式。有時,它是 Native AOT 的一個很好的替代品。
以下是 .NET 9 候選發布版(2024 年 9 月)的當前效能比較結果:
啟動時間
AOT 應用程式的啟動速度比託管版本快得多。對於 Stage1 和 Stage2 應用程式以及所有環境都是如此。結果範例:
設想 | 啟動時間(毫秒) |
---|---|
Stage2AotSpeedOpt | 100 |
Stage2Aot | 109 |
Stage2 | 528 |
工作集
Native AOT 應用程式的最大工作集小於託管版本。在 Linux 上,託管版本使用的 RAM 比 AOT 版本多約 1.5 - 2 倍。例如:
設想 | 最大工作集 (MB) |
---|---|
Stage1Aot | 56 |
Stage1AotSpeedOpt | 57 |
Stage1 | 126 |
在 Windows 上,差異較小。特別是對於 Stage2:
設想 | 最大工作集 (MB) |
---|---|
Stage2Aot | 152 |
Stage2AotSpeedOpt | 150 |
Stage2 | 167 |
每秒請求數
RPS 值越大意味著應用速度越快。輕量級 Stage1 應用程式通常每秒處理約 800-900K 請求。較大的 Stage2 應用程式僅處理約 200K 請求。
對於 Stage2 應用程序,.NET 版本在所有環境中都比 AOT 版本處理更多的請求。 Stage2AotSpeedOpt 版本的速度有時接近。但是,通常它位於 Stage2 和 Stage2Aot 之間。以下是典型結果:
設想 | RPS |
---|---|
Stage2 | 235,008 |
Stage2AotSpeedOpt | 215,637 |
Stage2Aot | 194,264 |
Stage1 應用程式在英特爾 Linux 和英特爾 Windows 上的結果相似。然而,在 Ampere Linux 上,AOT 擊敗了託管版本。 Ampere Linux 的範例結果:
設想 | RPS |
---|---|
Stage1AotSpeedOpt | 929,524 |
Stage1Aot | 912,344 |
Stage1 | 844,659 |
因此,環境和應用程式程式碼可能會顯著影響速度。執行自己的基準測試來估計 Native AOT 為您的專案帶來的好處是有意義的。讓我們在沒有 Microsoft 測試基礎架構的情況下編寫自訂基準測試。
對 Native AOT 應用程式進行基準測試
我們將使用兩種類型的基準。第一個基於 BenchmarkDotNet - 用於對 .NET 程式碼進行基準測試的流行函式庫。這些基準比較純粹的速度,不包括啟動時間。
第二個是基於hyperfine 工具。它允許比較兩個 shell 命令的執行時間。這些基準測試比較整體速度,包括啟動時間。
這裡我們不比較記憶體消耗。目前,BenchmarkDotNet 中的NativeMemoryProfiler
診斷器不支援 Native AOT 運行時。
hyperfine 目前也不追蹤記憶體使用情況。
您可以從 GitHub 上的 NativeAotBenchmarks 儲存庫下載原始程式碼。我們鼓勵您在您的環境中嘗試它們。本文介紹了配備 Intel Core i9-13900H 處理器和 16 Gb RAM 的 Windows 11 筆記型電腦的結果。
確保正確運行基準測試。以下是常見的建議:
- 使用發布版本。
- 關閉除基準測試進程之外的所有應用程式。例如,停用防毒軟體、關閉 Visual Studio 和 Web 瀏覽器。
- 保持筆記型電腦電源接通並使用最佳效能模式。
- 在比較的場景中使用相同的輸入資料。
測試用例
我們將對 .NET 8 中的 2 個場景進行基準測試:
1. 使用重複字元計數進行字串壓縮的簡單 C# 程式碼。例如,字串“aabccccccaaa”將變為“a2b1c5a3”:
string Compress(string s)
{
StringBuilder compressed = new(s.Length);
for (int i = 0; i < s.Length; ++i)
{
char c = s[i];
for (int j = i + 1; j <= s.Length; ++j)
{
if (j == s.Length || s[j] != c)
{
compressed.Append(c + $"{j - i}");
i = j - 1;
if (compressed.Length > s.Length)
return s;
break;
}
}
}
if (compressed.Length <= s.Length)
return compressed.ToString();
return s;
}
2. 使用Docotic.Pdf的較重的PDF到PNG轉換任務。
先決條件
安裝 .NET Native AOT 部署的先決條件。
安裝hyperfine執行對應的基準測試。
對於 PDF 到 PNG 基準測試,請在 下載 C# .NET PDF 函式庫 頁面取得免費的限時許可證金鑰。您需要在Helper.cs
中套用許可證金鑰。
BenchmarkDotNet
這些基準測試位於NativeAotBenchmarks
專案中。我們比較 RuntimeMoniker.NativeAot80 和 RuntimeMoniker.Net80
的結果。預設情況下,BenchmarkDotNet 使用OptimizationPreference=Speed
設定來建構 Native AOT 程式碼。
BenchmarkDotNet 執行 6 次或更多熱身迭代。這有助於 JIT 預編譯程式碼並收集一些統計資料。因此,此類基準將啟動時間排除在比較之外。
字串壓縮
字串壓縮的CompressString
基準使用具有重複字元的長字串。常見的錯誤是產生隨機字串。在這種情況下,Native AOT 和
.NET 8 的基準測試將使用不同的輸入字串。可以使用隨機字串,但您需要使用相同的種子初始化隨機產生器。
Native AOT 版本的運行速度比 .NET 8 版本快約 1.08 倍:
Method | Runtime | Mean | Error | StdDev |
---|---|---|---|---|
Compress | .NET 8.0 | 4.117 ms | 0.0553 ms | 0.0517 ms |
Compress | NativeAOT 8.0 | 3.809 ms | 0.0403 ms | 0.0377 ms |
PDF 轉 PNG
PDF 到 PNG 基準測試在記憶體中處理 PDF 文件。這允許排除與檔案系統的交互。磁碟的 I/O 操作可能會影響基準測試結果。
我們用兩個 PDF 文件測試速度。第一個 Banner Edulink One.pdf 更為複雜。它被轉換為 72 dpi PNG,需要更多時間進行處理。本文檔的 .NET 8 版本稍快:
Method | Runtime | Mean | Error | StdDev |
---|---|---|---|---|
Convert | .NET 8.0 | 1.103 s | 0.0156 s | 0.0146 s |
Convert | NativeAOT 8.0 | 1.167 s | 0.0160 s | 0.0149 s |
第二個文檔更小、更簡單。它被轉換為 300 dpi PNG。且速度幾乎相等:
Method | Runtime | Mean | Error | StdDev |
---|---|---|---|---|
Convert | .NET 8.0 | 290.1 ms | 5.78 ms | 6.88 ms |
Convert | NativeAOT 8.0 | 288.3 ms | 4.44 ms | 3.94 ms |
hyperfine
這些基準測試位於NativeAotTestApp
專案中。該項目不使用OptimizationPreference=Speed
設定。您可以在
NativeAotTestApp.csproj 中啟用它: <OptimizationPreference>Speed</OptimizationPreference>
使用 benchmark.bat 腳本在 Windows 上執行測試。您可以將其轉換為適用於基於 Unix/Linux 的作業系統的
Bash。該腳本建立同一應用程式的 .NET 8 和 Native AOT 版本。然後,它將它們的性能與類似命令進行比較:
hyperfine --warmup 3 "net8-app.exe" "native-aot-app.exe"
在 hyperfine 中運行預熱有助於在「熱」磁碟快取上啟動測試應用程式。與 BenchmarkDotNet 不同,hyperfine 預熱對 JIT 沒有幫助。因此,hyperfine 基準測試會比較應用程式的總速度,包括啟動時間。
我們的測試應用程式支援迭代計數參數。它允許在一個簡單的循環中多次重複相同的程式碼:
for (int i = 0; i < iterationCount; ++i)
CompressString(args);
這個想法是為了減少啟動時間差異的影響。重複相同的程式碼使 JIT 有機會收集更多運行時統計資料並產生更快的程式碼。
常見的情況如下。第一次,您透過一次迭代運行基準測試。 Native AOT 版本的運行速度要快得多。然後,您透過多次迭代運行相同的基準測試,兩個版本的總速度變得相等。這意味著啟動後,託管版本實際上更快。
字串壓縮
對於相同輸入字串壓縮的 100,000 次迭代,Native AOT 效能較好:
Benchmark 1: .NET 8 version (100000 iterations)
Time (mean ± σ): 151.5 ms ± 2.6 ms [User: 32.1 ms, System: 1.6 ms]
Range (min … max): 148.0 ms … 157.5 ms 19 runs
Benchmark 2: Native AOT version (100000 iterations)
Time (mean ± σ): 55.1 ms ± 3.1 ms [User: 15.0 ms, System: 2.1 ms]
Range (min … max): 51.6 ms … 65.9 ms 51 runs
Summary
Native AOT version ran 2.75 ± 0.16 times faster than .NET 8 version
但迭代 10,000,000 次後速度幾乎相同:
Benchmark 1: .NET 8 version (10000000 iterations)
Time (mean ± σ): 3.984 s ± 0.139 s [User: 2.946 s, System: 0.009 s]
Range (min … max): 3.790 s … 4.182 s 10 runs
Benchmark 2: Native AOT version (10000000 iterations)
Time (mean ± σ): 3.956 s ± 0.041 s [User: 2.848 s, System: 0.004 s]
Range (min … max): 3.888 s … 4.016 s 10 runs
Summary
Native AOT version ran 1.01 ± 0.04 times faster than .NET 8 version
PDF 轉 PNG
對於 Banner Edulink One.pdf 到 PNG 轉換的單次迭代,AOT 版本的運行速度比 .NET 8 版本快約 1.88 倍:
Benchmark 1: .NET 8 version (1 iteration)
Time (mean ± σ): 2.417 s ± 0.104 s [User: 1.334 s, System: 0.116 s]
Range (min … max): 2.295 s … 2.629 s 10 runs
Benchmark 2: Native AOT version (1 iteration)
Time (mean ± σ): 1.288 s ± 0.011 s [User: 0.573 s, System: 0.123 s]
Range (min … max): 1.274 s … 1.310 s 10 runs
對於 20 次迭代,速度差異可以忽略不計:
Benchmark 1: .NET 8 version (20 iterations)
Time (mean ± σ): 25.048 s ± 0.223 s [User: 13.278 s, System: 2.312 s]
Range (min … max): 24.751 s … 25.423 s 10 runs
Benchmark 2: Native AOT version (20 iterations)
Time (mean ± σ): 25.213 s ± 0.114 s [User: 12.661 s, System: 2.275 s]
Range (min … max): 25.042 s … 25.350 s 10 runs
Summary
.NET 8 version ran 1.01 ± 0.01 times faster than Native AOT version
對於 3BigPreview.pdf,Native AOT 版本即使進行 100 次迭代也更快:
Benchmark 1: .NET 8 version (100 iterations)
Time (mean ± σ): 10.009 s ± 0.152 s [User: 5.298 s, System: 0.567 s]
Range (min … max): 9.677 s … 10.189 s 10 runs
Benchmark 2: Native AOT version (100 iterations)
Time (mean ± σ): 8.336 s ± 0.070 s [User: 3.405 s, System: 0.505 s]
Range (min … max): 8.247 s … 8.459 s 10 runs
Summary
Native AOT version ran 1.20 ± 0.02 times faster than .NET 8 version
結論
與常規 .NET 相比,Native AOT 應用程式啟動速度更快。官方基準測試也顯示,AOT 應用程式的記憶體佔用量更小。
但啟動後,託管應用程式通常會表現出更好的速度。發生這種情況是因為 JIT 可以存取運行時資訊。在長時間運行的應用程式中,它可以基於動態設定檔引導最佳化和其他技術重新產生更有效的程式碼。
ASP.NET 基準測試可讓您從效能角度比較不同的配置。然而,結果取決於作業系統和處理器架構。您需要在目標環境中執行自己的基準測試來找到最佳部署配置。