在之前的一篇文章《看我是如何用C#编写一个小于8KB的贪吃蛇游戏》中,介绍了在.NET Core 3.0的环境下如何将贪吃蛇游戏降低到8KB。不过也有很多小伙伴提出了一些疑问和看法,主要是下面这几个方面:
- 
.NET Core 3.0可以做到这么小,那么.NET7表现会不会更好? 
- 
不敢在生产中用这样的方式,我看CoreRT这个仓库我看已经归档了。 
- 
这样子弄太麻烦了,有没有更简单的办法? 
今天笔者就给大家一一解答这些问题。.
.NET7下的贪吃蛇游戏
我们知道在.NET7中已经发布了NativeAOT正式的支持,经过.NET5、.NET6的迭代,NativeAOT已经基本成熟可用,那么在.NET7中重新编译这个游戏,有没有什么进步呢?让我们来看一看。
有外网条件的朋友可以看下方的这个GITHUB链接的代码,这个代码就是提交了升级.NET7 NativeAOT的实现:https://github.com/MichalStrehovsky/SeeSharpSnake/pull/24
使用.NET7单文件发布
csproj文件需要有一些小的改动。首先就是将对应的TargetFramework修改为net7.0版本。 
此时就已经完成.NET Core 3.1到NET7.0的迁移了,我们运行下面的命令,可以获得一个65MB大小的程序,这个和之前.NET Core 3.1没有什么区别。
dotnet publish -r win-x64 -c Release 

开启IL Linker
另外后面的.NET版本支持更好的程序集剪裁,也就是IL Linker工具,我们运行命令行时/p:PublishTrimmed=true选项就可以启用。
dotnet publish -r win-x64 -c Release /p:PublishTrimmed=true 

使用NativeAOT功能
<PublishAot>true</PublishAot>选项。我们加入了一个条件,在平时不开启,只有输入不同Mode的时候才开启。 
dotnet publish -r win-x64 -c Release /p:Mode=NativeAOT 

使用Moderate模式
csproj文件,让它支持Moderate模式,也就是使用<IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata>不生成完整的类型元数据,另外也用<IlcOptimizationPreference>Size</IlcOptimizationPreference>让编译器为程序大小进行优化,而不是速度。由于后面的模式也需要支持这个,所以加入了很多条件编译的选项。 
dotnet publish -r win-x64 -c Release /p:Mode=NativeAOT-Moderate 

进一步移除无关数据
接下来我们进一步移除无关的数据。
- 
使用 <EventSourceSupport>false</EventSourceSupport>关闭对EventSource的支持
- 
使用 <UseSystemResourceKeys>true</UseSystemResourceKeys>删除System.*程序集的异常消息。
- 
使用 <InvariantGlobalization>true</InvariantGlobalization>删除全球化特定的代码和数据。 
dotnet publish -r win-x64 -c Release /p:Mode=NativeAOT-High 

继续移除无关数据
- 
通过 <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>移除堆栈跟踪数据
- 
通过 <IlcInvariantGlobalization>true</IlcInvariantGlobalization>移除其它语言的支持
- 
通过 <IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>将相同的方法体进行合并。


关闭反射
<IlcDisableReflection>true</IlcDisableReflection>来关闭反射,移除掉一些反射的元数据。 
dotnet publish -r win-x64 -c Release /p:Mode=NativeAOT-ReflectionFree 

和.NET Core 3.0的对比
下图是.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已经归档。其实大可放心的使用,CoreRT关闭的原因也正如下面链接仓库里面说的一样,是代码已经合并到runtimelab/nativeaot项目中。https://github.com/dotnet/corert

而NativeAOT已经从实验室中毕业,合并到dotnet/runtime中了,也就是.NET7看到的<PublishAot>选项,可以关注下面的微软文档。https://learn.microsoft.com/zh-cn/dotnet/core/deploying/native-aot/
NoRuntime用起来很折腾
另外看到评论区大家吐槽的点就是后面那些骚操作看起来很麻烦,有没有更简单的方式?这个其实是有的,上篇文章的作者推出了bflat这个项目。
bflat是Roslyn(生成.NET可执行文件的"官方"C#编译器)和NativeAOT(née CoreRT)的混合物,NativeAOT(née CoreRT)是基于CoreCLR的.NET的提前编译器。因此,您可以使用高性能 CoreCLR GC 和本机代码生成器 (RyuJIT) 访问最新的 C# 功能。
bflat 将两个组件合并到一个用于 C# 的提前交叉编译器和运行时中。bflat目前可以针对:
- 
x64/arm64 基于 glibc 的 Linux(x64 (~CentOS 7) 上为 2.17 或更高版本,arm64 (~Ubuntu 18.04)上为 2.27 或更高版本) 
- 
arm64 基于 bionic 的 Linux(Android API 级别 21) 
- 
x64/arm64 Windows (Windows 7 或更高版本) 
- 
x64 UEFI(仅适用于 --stdlib:zero)
对基于 musl 的 Linux 的支持正在开发中。bflat 可以生成本机可执行文件,也可以生成可通过 FFI 从其他语言调用的本机共享库,下面是它的开源地址:https://github.com/bflattened/bflat
使用NoRuntime模式最小可以做到4KB大小,而且支持无操作系统裸机UEFI启动。

总结
我们可以惊喜的看到NativeAOT经过几年的发展已经逐步走向成熟,另外还有裸机可运行的C#程序,这给了我们很多的想象空间,可能有那么一天C#程序会运行在只有几百KB内存的物联网终端设备上,UEFI启动程序使用C#编写等等。
