如何在dotnet6的应用里面使用Crossgen2工具,给DLL生成AOT数据

我对几个应用进行了严格的启动性能评估,对比了.NET Framework和dotnet 6下的应用启动性能,非常符合预期。可以看出,在用户设备上,NGen 的 . NET Framework 可以提供非常优越的启动性能。此外,.NET Framework 本身就是系统组件的一部分。当很少有冷启动时,大多数 DLL 都会在系统中预热。在启动性能方面,.NET Framework 仍然比 dotnet 6 快很多。在破坏 .NET Framework 的运行时框架层的 NGen 后,可以发现 .NET Framework 的启动性能无法与之相比dotnet 6. 为了平衡dotnet 6下与.NET Framework的启动性能差异,引入了与NGen相同级别的ReadyToRun来提升整体性能。本文将告诉你如何在dotnet 6应用中使用Crossgen2工具为DLL生成AOT数据,提高应用启动性能

我希望这篇文章是最新的,概念正在改变,这篇文章写于 2022.05。如果您在写这篇文章后阅读了很长时间,请注意本文中已经过时的误导性知识

在开始之前,还请澄清一下概念

在 dotnet 中,这些概念正在发生变化并没有完全确定。在说dotnet中的AOT之前,我们首先要造谣。第一个谣言是AOT意味着更好的性能?其实使用AOT可以减少应用启动过程中IL转原生代码的损耗,但是通过Tiered Compilation技术,这部分的差别不会特别大,再加上dotnet 6引入的QuickJit技术可以进一步缩小差距。但即便如此,在启动性能方面,使用 AOT 还是很有优势的,因为启动过程对性能很敏感,大项目在启动过程中需要执行大量的代码逻辑,即使 JIT 更快更在 PGO 的帮助下,由于需要做大量的工作,在性能方面仍然不如使用 AOT。由于AOT是生产的静态逻辑,它只占用最小的平台集,不能像JIT那样根据运行设备动态优化。这就是JIT进入Tier 2优化后,运行时的性能远高于AOT的原因。道路。也就是说,在整个进程中使用AOT而不添加任何JIT,只会提高启动性能,但会降低运行进程的性能

那如果我想要启动的性能,那么运行进程的性能呢?这就是 ReadyToRun 技术的概念。调用DLL时,首先使用AOT技术,部分逻辑用JIT预运行,运行后的二进制逻辑也记录在DLL中。这样,在第一次调用该方法时,可以减少JIT的作用,尽可能地利用之前AOT的内容,从而提高应用启动性能。应用程序运行后,仍然运行JIT优化,这样可以兼顾启动性能和运行进程的性能。

ReadyToRun的概念如何实现?需要几种技术和工具,其中 Crossgen2 是 ReadyToRun 的工具。通过Crossgen2工具,可以将DLL静态AOT到DLL中

但是这种方式也不是没有缺点,就是将AOT的内容编译到DLL中会增加DLL的大小。 DLL 大小的增加会降低启动过程中读取文件的性能。另外,AOT和JIT流程的切换也需要判断逻辑。加上这部分损失后,对比一下 QuickJit 技术。事实上,Crossgen2 用于 ReadyToRun 并不能提高所有 DLL 的启动性能

为了解决以上问题,在dotnet中引入了PGO的概念。启动过程中调用的方法是有限的。如果能够知道应用程序启动过程中会调用哪些方法,并且只对这些方法进行AOT,那么对DLL大小的影响就会小很多。这是PGO需要解决的问题。通过引入PGO的概念,在应用程序运行过程中,我们可以了解应用程序启动过程中会遇到哪些IL逻辑,并记录这部分逻辑来指导ReadyToRun进程执行AOT方法。因此,AOT 进程不需要针对所有 IL 逻辑,而只针对应用程序启动进程需要使用的内容执行 AOT 进程。这样可以大大提高应用程序的启动性能。但是PGO能做的不仅仅是ReadyToRun的指导,还可以作为JIT进程让JIT提前知道哪些IL转换可以在后台线程中运行,从而实现更高的启动性能。需要注意的是,我问了几个老板,得知现在的PGO还是个玩具。虽然在性能评估上能取得不错的成绩,但还不具备在出版环境中使用的能力

AOT 无法反编译的谣言。如上所见,ReadyToRun技术依然保留了IL逻辑,只是在DLL中加入了AOT生成的二进制数据,从而减少了启动过程中JIT的损失。也就是说,如果使用ReadyToRun技术,应用程序可以有更快(不一定更快)的启动性能,同时也有原来运行进程的性能。但是能不能不可逆自然是不可能的,原来的IL代码还在,也就是说使用了ReadyToRun技术,没有额外的保护能力。第二个问题,如果使用纯AOT技术,能否实现代码保护能力?嗯,我可以再补充一点。如果对合作有困惑,感觉也差不多。如果要说抗裂能力,分两分,一个是60分,一个是70分,满分是100分。如果其他人看不懂,最好在代码中写垃圾。当我尽力而为的时候,我保证我自己也看不懂

回到正题,如何使用Crossgen2工具在dotnet中进行ReadyToRun来提升应用性能?不要被官方骗了,如果你只是在csproj上或者发布的时候加上ReadyToRun的命令参数,恭喜你,你真的用上了Corssgen2工具。但是优化呢?刚刚优化了入口组件

如果真的想有比较大的优化,需要使用Crossgen2工具对除了入口程序集以外的其他程序集进行ReadyToRun,会有比较大的提升。例如,在我的一个大型应用程序中,WPF框架中大约有十分之一的模块在启动过程中被触摸过一次。使用 JitInfo.GetCompiledMethodCount,我了解到在第一个窗口 Show 出现之前有 50,000 次方法调用。这个应用的入口组装比例太小了。如果使用官方的方法,只对入口程序集执行ReadyToRun,性能会被.NET Framework完全滥用

为了使 dotnet 6 应用程序的启动性能与 .NET Framework 应用程序相当,可以使用 ReadyToRun 对 .NET Framework 的 NGen 技术进行基准测试。下面将告诉你如何使用Crossgen2工具对DLL进行ReadyToRun以提高启动性能

默认的 Crossgen2 工具是使用 NuGet 分发的 DotnetPlatform 类型的 NuGet 包,其中包含独立分发的 Crossgen2 工具。换句话说,这个工具可以在 %localappdata%…..nugetpackagesmicrosoft.netcore.app.crossgen2.win-x64 中找到。如果找不到,请尝试使用 dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true 命令让 dotnet 下载 Crossgen2 供您构建 ReadyToRun

以上Crossgen2工具都放在microsoft.netcore.app.crossgen2.win-x64文件夹下,这里win-x64不是指Crossgen2工具的能力,不代表工具在这个文件夹中只能Build for win-x64。相反,该工具本身是 win-x64。该工具能够为其他平台构建 AOT。也就是说,在 Windows 32 位系统中,会被拉取的工具是 microsoft.netcore.app.crossgen2.win-x86 包

进入版本号文件夹,然后进入Tools文件夹,找到Crossgen2.exe可执行文件,就是工具文章。例如,在我的设备上,刀具路径是

C:Userslindexi.nugetpackagesmicrosoft.netcore.app.crossgen2.win-x646.0.5toolsCrossgen2.exe

下面将告诉你如何使用这个工具

使用此工具时建议传入的参数是 rsp 文件。大概的命令行调用如下

C:Userslindexi.nugetpackagesmicrosoft.netcore.app.crossgen2.win-x646.0.5toolsCrossgen2.exe "@C:lindexiFxxF1.rsp"

具体参数放在rsp文件中,大致内容如下

--targetos:windows
--targetarch:x86
--pdb
-O
-r:"C:Program Files (x86)dotnetsharedMicrosoft.NETCore.App6.0.5api-ms-win-core-console-l1-1-0.dll"
-r:"C:Program Files (x86)dotnetsharedMicrosoft.NETCore.App6.0.5api-ms-win-core-console-l1-2-0.dll"
-r:"C:Program Files (x86)dotnetsharedMicrosoft.NETCore.App6.0.5api-ms-win-core-datetime-l1-1-0.dll"
-r:"C:Program Files (x86)dotnetsharedMicrosoft.NETCore.App6.0.5api-ms-win-core-debug-l1-1-0.dll"
-r:"C:Program Files (x86)dotnetsharedMicrosoft.NETCore.App6.0.5api-ms-win-core-errorhandling-l1-1-0.dll"
-r:"C:Program Files (x86)dotnetsharedMicrosoft.NETCore.App6.0.5api-ms-win-core-fibers-l1-1-0.dll"
-r:"C:Program Files (x86)dotnetsharedMicrosoft.NETCore.App6.0.5api-ms-win-core-file-l1-1-0.dll"
-r:"C:Program Files (x86)dotnetsharedMicrosoft.NETCore.App6.0.5api-ms-win-core-file-l1-2-0.dll"
--out:"C:UserslindeAppDataLocalTempCrossgen2Crossgen2KokicakawheeyeeWhemhedawfelawnemhel.dll"
C:lindexiCodeemptyKokicakawheeyeeWhemhedawfelawnemhelKokicakawheeyeeWhemhedawfelawnemhelbinreleasenet6.0-windowswin-x86publishKokicakawheeyeeWhemhedawfelawnemhel.dll

大致由以下几部分组成。每一行都是一个独立的参数,内容如下

构建 rsp 文件并调用 Crossgen2 工具作为参数以完成程序集的 ReadyToRun 过程。对多个程序集重复上述过程多次

需要注意的是,为 ReadyToRun 调用 Crossgen2 工具并不一定会提高启动性能。这是一个需要衡量的过程。每个DLL在调用ReadyToRun的Crossgen2工具后都会修改文件大小,整个修改也会影响启动性能。建议优化应用启动性能,并进行如下充分测量

对每个 DLL 使用一次 Crossgen2 工具,包括框架层 DLL。然后逐个替换每个DLL,测量应用启动性能。如果您发现某些DLL ReadyToRun 但降低了启动性能,或者某些DLL 增加的文件大小与启动性能优化相比不划算,那么不要优化这些DLL

以下是对底层dotnet运行时和WPF框架的DLL进行ReadyToRun优化后,测试对walterlv应用启动性能的影响。值得一提的是,对于不同的应用,测试数据会有很大的差异,核心原因是不同的应用启动进程会访问不同的模块

这个数据没有太大的参考价值,因为上面的结果会因不同的应用而有所不同。如果要使用ReadyToRun技术来提高应用程序的启动性能,还必须衡量每个DLL在ReadyToRun之后对启动性能的影响。如果有时间,还可以衡量组合多个 DLL 优化对启动性能的影响

我团队的一个大规模应用通过ReadyToRun技术优化,启动性能提升30%

但还必须注意,并非所有应用程序都可以使用 ReadyToRun 优化启动性能。比如我的一个小应用,只要使用ReadyToRun技术,启动性能基本就降低了。一般来说,ReadyToRun 技术的使用需要性能测量

参考文档

WPF dotnet 使用原生镜像原生优化的 dotnet 框架二进制文件

WPF 通过 ReadyToRun 提高性能

关于crossgen2的对话——.NET博客

runtime/crossgen2-compilation-structure-enhancements.md 在主 dotnet/runtime

runtime/Program.cs 位于主 dotnet/runtime

构建配置设置 – .NET Microsoft Docs

使用 PGO 提升 .NET 程序性能 – hez2010 – 博客园

JitInfo.GetCompiledMethodCount(Boolean) 方法 (System.Runtime) Microsoft Docs

© 版权声明
THE END
喜欢就支持一下吧
点赞191赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容