在之前的一篇文章《看我是如何用C#編寫一個小於8KB的貪吃蛇遊戲》中,介紹了在.NET Core 3.0的環境下如何將貪吃蛇遊戲降低到8KB。不過也有很多小夥伴提出了一些疑問和看法,主要是下面這幾個方面:
今天筆者就給大家一一解答這些問題。
我們知道在.NET7中已經發布了NativeAOT正式的支援,經過.NET5、.NET6的迭代,NativeAOT已經基本成熟可用,那麼在.NET7中重新編譯這個遊戲,有沒有什麼進步呢?讓我們來看一看。
有外網條件的朋友可以看下方的這個GITHUB連結的程式碼,這個程式碼就是提交了升級.NET7 NativeAOT的實現:
https://github.com/MichalStrehovsky/SeeSharpSnake/pull/24
為了達到我們的目的,對於這個專案的csproj
檔案需要有一些小的改動。首先就是將對應的TargetFramework
修改為net7.0
版本。
此時就已經完成.NET Core 3.1到NET7.0的遷移了,我們執行下面的命令,可以獲得一個65MB大小的程式,這個和之前.NET Core 3.1沒有什麼區別。
dotnet publish -r win-x64 -c Release
另外後面的.NET版本支援更好的程式集剪裁,也就是IL Linker工具,我們執行命令列時/p:PublishTrimmed=true
選項就可以啟用。
dotnet publish -r win-x64 -c Release /p:PublishTrimmed=true
此時我們可以發現,只有11MB大小了,比.NET Core 3.0時代的25MB降低了一半多。
然後我們就要開始使用.NET7的NativeAOT功能,需要在專案檔案中加入<PublishAot>true</PublishAot>
選項。我們加入了一個條件,在平時不開啟,只有輸入不同Mode的時候才開啟。
dotnet publish -r win-x64 -c Release /p:Mode=NativeAOT
此時可以獲得一個2.86MB大小的程式,比.NET Core 3.0時代的4.7MB要小了快一半。
繼續修改csproj
檔案,讓它支援Moderate模式,也就是使用<IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata>
不生成完整的型別後設資料,另外也用<IlcOptimizationPreference>Size</IlcOptimizationPreference>
讓編譯器為程式大小進行優化,而不是速度。由於後面的模式也需要支援這個,所以加入了很多條件編譯的選項。
dotnet publish -r win-x64 -c Release /p:Mode=NativeAOT-Moderate
結果和上面的一樣的2.86MB,也就是說現在NativeAOT應該預設就是Moderate模式。
接下來我們進一步移除無關的資料。
<EventSourceSupport>false</EventSourceSupport>
關閉對EventSource的支援<UseSystemResourceKeys>true</UseSystemResourceKeys>
刪除 System.*
程式集的異常訊息。<InvariantGlobalization>true</InvariantGlobalization>
刪除全球化特定的程式碼和資料。dotnet publish -r win-x64 -c Release /p:Mode=NativeAOT-High
此時我們再次釋出,可以看到大小已經降低到了2.15MB,比.NET Core 3.0時的3.0MB降低了快30%。
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
移除堆疊跟蹤資料<IlcInvariantGlobalization>true</IlcInvariantGlobalization>
移除其它語言的支援<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
將相同的方法體進行合併。只為我們省下了幾百KB,此時大小來到了1.88MB。
接下來我們可以繼續使用<IlcDisableReflection>true</IlcDisableReflection>
來關閉反射,移除掉一些反射的後設資料。
dotnet publish -r win-x64 -c Release /p:Mode=NativeAOT-ReflectionFree
關閉反射後,大小來到了1.21MB,這應該是不用騷操作能達到的最小大小了。
下圖是.NET7和.NET Core 3.0在不同模式下大小的對比,可以看到經過.NET 5.0、.NET 6.0的發展,NativeAOT變得更加成熟了。
模式 | .NET Core 3.0 | .NET7.0 | 幅度 |
---|---|---|---|
單檔案發布 | 65MB | 65MB | 0% |
IL Linker剪裁 | 25MB | 11MB | -56% |
NativeAOT | 4.7MB | 2.86MB | -40% |
NativeAOT-High | 3.0MB | 1.88MB | -38% |
關閉反射 | 1.21MB | 1.21MB | 0% |
在部落格園的評論中,看到有一位朋友留言,說不敢在生產環境中使用,而且CoreRT已經歸檔。其實大可放心的使用,CoreRT關閉的原因也正如下面連結倉庫裡面說的一樣,是程式碼已經合併到runtimelab/nativeaot專案中。
https://github.com/dotnet/corert
而NativeAOT已經從實驗室中畢業,合併到dotnet/runtime中了,也就是.NET7看到的<PublishAot>
選項,可以關注下面的微軟檔案。
https://learn.microsoft.com/zh-cn/dotnet/core/deploying/native-aot/
另外看到評論區大家吐槽的點就是後面那些騷操作看起來很麻煩,有沒有更簡單的方式?這個其實是有的,上篇文章的作者推出了bflat
這個專案。
bflat是Roslyn(生成.NET可執行檔案的"官方"C#編譯器)和NativeAOT(née CoreRT)的混合物,NativeAOT(née CoreRT)是基於CoreCLR的.NET的提前編譯器。因此,您可以使用高效能 CoreCLR GC 和本機程式碼生成器 (RyuJIT) 存取最新的 C# 功能。
bflat 將兩個元件合併到一個用於 C# 的提前交叉編譯器和執行時中。bflat目前可以針對:
--stdlib:zero
)對基於 musl 的 Linux 的支援正在開發中。bflat 可以生成本機可執行檔案,也可以生成可通過 FFI 從其他語言呼叫的本機共用庫,下面是它的開源地址:
https://github.com/bflattened/bflat
使用NoRuntime模式最小可以做到4KB大小,而且支援無作業系統裸機UEFI啟動。
我們可以驚喜的看到NativeAOT經過幾年的發展已經逐步走向成熟,另外還有裸機可執行的C#程式,這給了我們很多的想象空間,可能有那麼一天C#程式會執行在只有幾百KB記憶體的物聯網終端裝置上,UEFI啟動程式使用C#編寫等等。
相信大家在開發中經常會遇到一些效能問題,苦於沒有有效的工具去發現效能瓶頸,或者是發現瓶頸以後不知道該如何優化。之前一直有讀者朋友詢問有沒有技術交流群,但是由於各種原因一直都沒建立,現在很高興的在這裡宣佈,我建立了一個專門交流.NET效能優化經驗的群組,主題包括但不限於:
希望能有更多志同道合朋友加入,分享一些工作中遇到的.NET效能問題和寶貴的效能分析優化經驗。目前一群已滿,現在開放二群,可以直接掃碼進入。
如果提示已經達到200人,可以加我微信,我拉你進群: ls1075
另外也建立了QQ群,群號: 687779078,歡迎大家加入。