織夢(mèng)整形醫(yī)院網(wǎng)站開(kāi)發(fā)江門(mén)網(wǎng)站優(yōu)化公司
????????在軟件開(kāi)發(fā)領(lǐng)域,優(yōu)化性能和簡(jiǎn)化效率仍然至關(guān)重要。.NET 平臺(tái)二十年來(lái)不斷創(chuàng)新,為開(kāi)發(fā)人員提供了構(gòu)建彈性且高效的軟件解決方案的基礎(chǔ)架構(gòu)。
????????與本機(jī) AOT(提前)編譯相結(jié)合,取得了顯著的進(jìn)步。本文深入研究.NET Native AOT,揭示它的工作原理、優(yōu)點(diǎn)以及它的各種應(yīng)用場(chǎng)景。
什么是 .NET 本機(jī) AOT?
????????.NET 本機(jī)提前 (AOT) 編譯是 .NET 平臺(tái)中的一項(xiàng)前沿進(jìn)步。通過(guò) AOT,C# 代碼將在開(kāi)發(fā)人員計(jì)算機(jī)上編譯為本機(jī)代碼。這與在運(yùn)行時(shí)將代碼編譯為本機(jī)代碼的傳統(tǒng)方法形成鮮明對(duì)比。
下面的架構(gòu)說(shuō)明了這一點(diǎn)。.NET 傳統(tǒng)編譯涉及兩個(gè)步驟:
????????1、C# 編譯生成包含中間語(yǔ)言 (IL)代碼的 DLL 文件。這樣的 DLL 稱為.NET 程序集。
????????2、執(zhí)行 .NET 程序時(shí),.NET 運(yùn)行時(shí)(CLR 公共語(yǔ)言運(yùn)行時(shí))會(huì)加載 .NET 程序集。CLR 的子系統(tǒng)負(fù)責(zé)將 IL 代碼編譯為由 CPU 直接執(zhí)行的本機(jī)代碼。這個(gè)子系統(tǒng)被命名為JIT(Just-In-Time)編譯器。它之所以得名是因?yàn)樗鼉H在首次調(diào)用某個(gè)方法時(shí)才編譯該方法的 IL 代碼。
????????另一方面,.NET Native AOT 編譯由一個(gè)步驟組成。在開(kāi)發(fā)人員的機(jī)器上將 C# 源代碼編譯為本機(jī)代碼。此過(guò)程涉及將 C# 代碼轉(zhuǎn)換為 IL 代碼,然后轉(zhuǎn)換為 Native 代碼,形成兩步編譯過(guò)程。但這是一個(gè)實(shí)現(xiàn)細(xì)節(jié)。這就是下面架構(gòu)中 AOT .NET 程序集框呈灰色的原因。
.NET 本機(jī) AOT 的優(yōu)點(diǎn)
.NET 本機(jī)提前 (AOT) 編譯帶來(lái)了一系列優(yōu)勢(shì):
????????增強(qiáng)的性能:通過(guò)將代碼預(yù)編譯為本機(jī)機(jī)器指令,.NET Native AOT 顯著縮短了啟動(dòng)時(shí)間并提高了應(yīng)用程序的整體性能。運(yùn)行時(shí)期間沒(méi)有 JIT 編譯開(kāi)銷意味著執(zhí)行速度更快,從而提供更流暢的用戶體驗(yàn)。
????????簡(jiǎn)化部署: AOT 編譯的應(yīng)用程序通常會(huì)生成具有零或更少依賴項(xiàng)的獨(dú)立可執(zhí)行文件。這簡(jiǎn)化了部署過(guò)程,使您可以更輕松地跨各種平臺(tái)和設(shè)備分發(fā)應(yīng)用程序,而無(wú)需額外安裝或運(yùn)行時(shí)組件。
更小的應(yīng)用程序大小:通過(guò)修剪掉不必要的代碼,AOT 可以大大減小應(yīng)用程序的大小。這不僅節(jié)省了存儲(chǔ)空間,還優(yōu)化了應(yīng)用程序的內(nèi)存占用,這在移動(dòng)設(shè)備或物聯(lián)網(wǎng)設(shè)備等資源受限的環(huán)境中尤其重要。
????????增強(qiáng)的知識(shí)產(chǎn)權(quán)保護(hù): AOT 編譯將源代碼轉(zhuǎn)換為優(yōu)化的機(jī)器代碼,使逆向工程嘗試更具挑戰(zhàn)性。與可以輕松反編譯為原始 C# 代碼的 IL 代碼相比,生成的本機(jī)代碼更加混亂且難以破譯。這增強(qiáng)了應(yīng)用程序中嵌入的敏感算法、業(yè)務(wù)邏輯和專有方法的安全性。
.NET Native AOT 的缺點(diǎn)
AOT 帶來(lái)的好處不可避免地伴隨著某些缺點(diǎn)。他們來(lái)了:
????????特定于平臺(tái)的編譯: .NET Native AOT 生成特定于平臺(tái)的本機(jī)代碼,針對(duì)特定的體系結(jié)構(gòu)或操作系統(tǒng)進(jìn)行定制。例如,與常規(guī) .NET 程序集不同,使用 AOT 在 Windows 上生成的可執(zhí)行文件無(wú)法在 Linux 上運(yùn)行。
????????不支持跨操作系統(tǒng)編譯:例如,您無(wú)法從 Windows 機(jī)器編譯 Linux 本機(jī)版本,反之亦然。
????????部分支持Reflection: Reflection依賴于動(dòng)態(tài)代碼生成和運(yùn)行時(shí)類型發(fā)現(xiàn),這與AOT編譯代碼的預(yù)編譯和靜態(tài)性質(zhì)相沖突。然而,我們將在本文末尾看到,通常的反射用法與 AOT 配合得很好。
????????需要 AOT 兼容的依賴項(xiàng): AOT 編譯要求項(xiàng)目中使用的所有庫(kù)和依賴項(xiàng)都與 AOT 兼容。依賴反射、運(yùn)行時(shí)代碼生成或其他動(dòng)態(tài)行為的庫(kù)可能與 AOT 不兼容,從而可能導(dǎo)致沖突或運(yùn)行時(shí)錯(cuò)誤。
????????增加構(gòu)建時(shí)間: AOT 編譯涉及在構(gòu)建過(guò)程中預(yù)先生成本機(jī)代碼。此附加步驟可以顯著增加構(gòu)建時(shí)間,特別是對(duì)于具有大量代碼庫(kù)的大型項(xiàng)目或應(yīng)用程序。
????????需要 C++ 桌面開(kāi)發(fā)工具: AOT 只能在安裝了這些工具的情況下進(jìn)行編譯,這些工具在您的硬盤(pán)上最多可達(dá) 7GB。
.NET 本機(jī) AOT 實(shí)際應(yīng)用
????????使用 Visual Studio 創(chuàng)建 .NET 本機(jī) AOT Web 應(yīng)用程序,啟動(dòng) Visual Studio 2022 v17.8(或更高版本)并選擇從模板ASP.NET Core Web API(本機(jī) AOT)創(chuàng)建項(xiàng)目。
????????該模板為我們生成一個(gè) .NET 8 ASP.NET Core Web API 應(yīng)用程序,我們可以看到 .csproj 文件包含<PublishAot>true</PublishAot>。
編譯 .NET Web 應(yīng)用程序
????????如果我們編譯應(yīng)用程序,我們可以看到在 .dll 下生成了一個(gè) DLL 文件.\bin\debug\net8.0。此 DLL 是包含 IL 代碼的常規(guī) .NET 程序集。我們可以使用 ILSpy 對(duì)其進(jìn)行反編譯,如下圖所示:
執(zhí)行.NET Web應(yīng)用程序
????????此時(shí),沒(méi)有發(fā)生AOT編譯。C# 代碼在不到一秒的時(shí)間內(nèi)就被編譯為 IL 代碼。.NET Web 應(yīng)用程序可以按原樣執(zhí)行:
使用 .NET Native AOT 編譯 Web 應(yīng)用程序
????????要使用 .NET Native AOT 編譯 Web 應(yīng)用程序,您必須dotnet publish 在包管理器控制臺(tái)中鍵入:
????????這次這個(gè)小應(yīng)用程序的編譯花費(fèi)了 11 秒。我們獲得一個(gè)大小為 9MB 的可執(zhí)行文件。該文件無(wú)法使用 ILSpy 反編譯,因?yàn)樗皇?.NET 程序集。該文件僅包含本機(jī)代碼。還生成一個(gè) 70MB PDB 文件,將本機(jī)代碼與源代碼鏈接起來(lái)以進(jìn)行調(diào)試。
????????可執(zhí)行文件可以在任何 Windows x64 系統(tǒng)上按原樣部署和執(zhí)行。部署可以更簡(jiǎn)單嗎?我不這么認(rèn)為!
.NET 本機(jī) AOT 權(quán)衡
現(xiàn)在是評(píng)估選項(xiàng)的時(shí)候了:
????????1、一方面,我們?cè)诓坏揭幻氲臅r(shí)間內(nèi)生成了一個(gè)可在所有平臺(tái)上運(yùn)行的 40KB DLL。但它需要預(yù)安裝.NET 8.0。
????????2、另一方面,我們?cè)?11 秒內(nèi)編譯出一個(gè) 9MB 的可執(zhí)行文件。它可以在任何 Windows x64 系統(tǒng)上運(yùn)行,無(wú)需任何先決條件。此外,這個(gè)本機(jī)版本的啟動(dòng)速度更快。對(duì)于任何足夠復(fù)雜的 Web 應(yīng)用程序,我們都可以期待更多的性能提升。
????????人們可能會(huì)認(rèn)為 9MB 比 40KB 大得多!但安裝 .NET 8.0 需要的磁盤(pán)空間遠(yuǎn)多于 9MB。僅目錄C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0 ?就有 70MB,而且還有更多東西需要安裝。因此 AOT 導(dǎo)致:
3、緊湊的容器鏡像在容器化部署設(shè)置中特別有用
4、由于圖像尺寸較小,部署時(shí)間縮短
以下是本文中值得引用的評(píng)論:
????????這是怎樣的前沿進(jìn)步?在 .NET 平臺(tái)引入之前的半個(gè)世紀(jì)里,這種軟件的生產(chǎn)方式已經(jīng)存在。
這是我的答案:因?yàn)槟阃瑫r(shí)擁有了兩個(gè)世界。
????????1、一方面,與 C/C++ 相比,現(xiàn)代語(yǔ)言和平臺(tái)通過(guò)運(yùn)行時(shí)帶來(lái)了許多便利
????????2、另一方面,原始的本機(jī)可執(zhí)行文件無(wú)需在生產(chǎn)計(jì)算機(jī)上安裝平臺(tái)。
????????暫停一下,你就會(huì)意識(shí)到這個(gè)單一可執(zhí)行文件有多酷。這 9MB 不僅包含 Web 應(yīng)用程序代碼。但它包含一些 CLR 代碼(垃圾收集、庫(kù)加載……)、常用的 .NET 類型(字符串、整數(shù)……)和 API 代碼(如WebApplication.CreateSlimBuilder()【W(wǎng)ebApplication.CreateSlimBuilder Method (Microsoft.AspNetCore.Builder) | Microsoft Learn】和 JSON 序列化器代碼)。
????????下面的依賴關(guān)系圖(通過(guò) NDepend 獲得【Visual Studio - Explore existing .net code architecture】)顯示了此 Web 應(yīng)用程序代碼及其依賴關(guān)系,全部打包在 9MB 位中!
用反射破壞.NET Native AOT?
????????我努力嘗試用 Reflection 來(lái)破壞 .NET Native AOT,但失敗了。這是一個(gè)好消息,AOT 支持很多 Reflection API。以下是與 AOT 配合使用的代碼:
// Reflection usage A
System.Type type = new object().GetType();
System.Reflection.MethodInfo methodInfo = type.GetMethod("ToString");
string str = (string)methodInfo.Invoke("Walk the dog", BindingFlags.Default, null, new object?[0], null);
Console.WriteLine("Reflection usage A:" + str);// Reflection usage B
var listOfString = typeof(List<>).MakeGenericType(new Type[] { typeof(string) });
var list = Activator.CreateInstance(listOfString) as List<string>;
list.Add("hello");
PropertyInfo prop = listOfString.GetProperty("Count");
int count = (int)prop.GetMethod.Invoke(list, new object?[0]);
Console.WriteLine("Reflection usage B:" + count);// Reflection usage C
Assembly assembly = Assembly.GetExecutingAssembly();
Type[] types = assembly.GetTypes();
foreach (Type type1 in types.Take(5)) {Console.WriteLine("Reflection usage C:" + type1.FullName);
}// Reflection usage D
Type unknownType = Type.GetType("System.String");
ParameterExpression param = Expression.Parameter(unknownType, "x");
MethodInfo method = unknownType.GetMethod("ToLower", Type.EmptyTypes);
Expression call = Expression.Call(param, method);
var lambda = Expression.Lambda(call, param).Compile();
var result = lambda.DynamicInvoke("HELLO");
Console.WriteLine("Reflection usage D:" + result);
????????我們dotnet publish有時(shí)會(huì)收到一些警告,但它確實(shí)有效!
.NET 8 對(duì)本機(jī) AOT 的支持
支持這些:
1、Middleware 中間件
2、Minimal APIs 最小?API
3、gRPC?遠(yuǎn)程過(guò)程調(diào)用
4、Kestrel HTTP Server
5、Authorization 授權(quán)
6、JWT Authentication?認(rèn)證
7、CORS 跨域資源共享
8、HealthChecks 健康檢查
9、OutputCaching 輸出緩存
10、RequestDecompression 請(qǐng)求解壓
11、ResponseCaching 響應(yīng)緩存
12、ResponseCompression 響應(yīng)壓縮
13、StaticFiles 靜態(tài)文件
14、WebSockets
15、ADO.NET
16、PostgreSQL
17、Dapper AOT
18、SQLite
尚不支持這些:
1、ASP.NET Core MVC
2、WebAPI
3、SignalR
4、Blazor Server,
5、Razor Pages,
6、Session, Spa
7、Entity Framework Core
結(jié)論
????????.NET Native AOT 代表了優(yōu)化 .NET 應(yīng)用程序的關(guān)鍵一步。這種編譯為本機(jī)代碼的過(guò)程可以最大限度地減少運(yùn)行時(shí)對(duì)即時(shí) (JIT) 編譯的依賴,從而提高性能。由此帶來(lái)的好處包括更快的執(zhí)行速度、減少的部署開(kāi)銷以及提高可擴(kuò)展性的潛力,使其成為提高 .NET 開(kāi)發(fā)領(lǐng)域效率和性能的絕佳選擇。
????????借助 .NET 8,Native AOT 已經(jīng)相當(dāng)成熟,可以在生產(chǎn)中使用。我們當(dāng)然可以希望微軟能夠繼續(xù)改進(jìn)AOT支持。