.NET 6控制台应用Crossgen/R2R/AOT预编译未生效问题咨询
问题根因
你测到的首次调用9ms耗时,根本不是自定义代码的JIT开销,之前试的方案没效果,一半是操作有误,一半是测试逻辑本身有问题:
- Crossgen2操作错误:执行crossgen2时仅用
-r .指定当前目录为引用路径,没有把.NET 6运行时的基础程序集(System.Runtime、System.Console等)加入引用列表,生成的预编译映像不完整,运行时不会加载对应的原生代码段,最终还是走JIT编译流程。 - ReadyToRun方案跑错了产物:配置
PublishReadyToRun=true发布时,你指定的输出目录是bin/readyToRun,后续执行时路径写的是/home/root/src/Tester/readyToRun/Tester.dll,少了bin层级,实际运行的根本不是带预编译信息的发布产物。 - 单文件+裁剪方案本身不做预编译:裁剪只会移除未被使用的IL代码,单文件发布只是把所有依赖打包成单个文件,两个参数都不会触发提前编译,没有效果是正常的。
- NativeAOT方案完全用错了产物:.NET 6下NativeAOT发布完成后,输出目录里不会存在
Tester.dll,会直接生成无依赖的原生可执行文件Tester,你还去执行dll文件,自然完全没用到AOT编译的结果。 - 测试逻辑有干扰:代码里留了3秒空转,但.NET的分层编译预热、基础类型加载、Console类首次调用时的控制台句柄初始化等成本,和自定义代码的JIT开销混在了一起,测到的9ms里绝大多数是运行时和系统API的初始化成本,不是JIT编译你写的那几行业务代码的耗时。
可行预编译方案
方案1:正确配置ReadyToRun(最接近原ngen的使用体验,兼容性最好)
这个方案和.NET Framework时代ngen的效果最接近,对现有代码的兼容性极高,绝大多数项目不需要改代码就能用:
- 执行发布命令,注意必须用自包含模式,同时开启复合R2R编译,把依赖的所有框架程序集也一起做预编译,覆盖基础库的首次调用开销:
cd /home/root/src/Tester dotnet publish -c Release -r linux-x64 --self-contained=true -p:PublishReadyToRun=true -p:PublishReadyToRunComposite=true -o bin/r2r_publish
- 运行时直接执行发布目录下生成的原生可执行文件,不要用
dotnet xxx.dll的方式启动,避免框架加载的额外开销:
chmod +x /home/root/src/Tester/bin/r2r_publish/Tester /home/root/src/Tester/bin/r2r_publish/Tester
正确配置后,自定义代码和所有依赖框架的IL都会提前编译成本机码,运行时不需要再执行JIT,首次方法调用的耗时会降到1ms以内。
方案2:正确使用NativeAOT(完全无JIT,启动速度最快)
.NET 6的NativeAOT属于预览功能,发布后生成纯原生可执行文件,完全不需要.NET运行时,整个程序没有JIT过程,启动速度是所有方案里最快的,但对反射、动态代码生成的支持有限,不适合有大量动态逻辑的项目:
- 先修改项目文件
Tester.csproj,开启AOT兼容配置:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <PublishAot>true</PublishAot> <IsAotCompatible>true</IsAotCompatible> </PropertyGroup> </Project>
- 执行发布命令:
cd /home/root/src/Tester dotnet publish -c Release -r linux-x64 --self-contained=true -o bin/aot_publish
- 发布完成后,直接给
bin/aot_publish目录下的Tester文件加执行权限运行即可,不需要安装任何.NET依赖。
测试优化建议
如果要准确测量JIT开销,不要把系统API和运行时的初始化成本算入统计:可以在3秒空转阶段先执行一次Console.WriteLine("warmup")做预热,提前走完控制台初始化、基础类型加载的流程,再启动秒表测量目标方法的耗时,得到的结果才是真实的JIT开销。
内容的提问来源于stack exchange,提问作者SimpleShadow
相关产品推荐
相关产品推荐

