通过将应用程序程序集编译为 ReadyToRun (R2R) 格式,可以改进 .NET 应用程序启动时间和延迟。 R2R 是预先编译(AOT)的一种形式。
R2R 二进制文件通过减少应用程序加载时实时 (JIT) 编译器需要执行的工作量来改进启动性能。 二进制文件包含与 JIT 将生成的内容类似的本机代码。 但是,R2R 二进制文件更大,因为它们包含中间语言 (IL) 代码(某些情况下仍需要此代码)和相同代码的本机版本。 仅当发布面向特定运行时环境(RID)(例如 Linux x64 或 Windows x64)的应用时,R2R 才可用。
若要将项目编译为 ReadyToRun,应用程序必须发布,并将 PublishReadyToRun 属性设置为 true
。
可通过两种方法将应用发布为 ReadyToRun:
将 PublishReadyToRun 标志直接指定到 dotnet publish 命令。 有关详细信息,请参阅 dotnet publish。
dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true
指定项目中的属性。
- 将
<PublishReadyToRun>
设置添加到项目。
<PropertyGroup> <PublishReadyToRun>true</PublishReadyToRun> </PropertyGroup>
- 发布没有任何特殊参数的应用程序。
dotnet publish -c Release -r win-x64
- 将
使用 ReadyToRun 功能的影响
预先编译对应用程序性能有复杂的性能影响,这很难预测。 一般情况下,程序集的大小将增加到两到三倍。 文件的物理大小增加可能会降低从磁盘加载程序集的性能,并增加进程的工作集。 然而,作为交换,运行时编译的方法数量通常会大幅减少。 结果是,大多数具有大量代码的应用程序都从启用 ReadyToRun 中获得较大的性能优势。 由于 .NET 运行时库已使用 ReadyToRun 预编译,具有少量代码的应用程序可能无法通过启用 ReadyToRun 显著改进。
此处讨论的启动改进不仅适用于应用程序启动,也适用于应用程序中第一次使用任何代码。 例如,ReadyToRun 可用于减少 ASP.NET 应用程序中首次使用 Web API 的响应延迟。
通过分层编译进行交互
预先生成的代码优化程度不如 JIT 生成的代码。 若要解决此问题,分层编译会将常用的 ReadyToRun 方法替换为 JIT 生成的方法。
如何选择预编译的一组程序集?
SDK 将预编译随应用程序一起分发的程序集。 对于独立应用程序,这组程序集将包括框架。 C++/CLI 二进制文件不符合 ReadyToRun 编译的条件。
若要从 ReadyToRun 处理中排除特定程序集,请使用 <PublishReadyToRunExclude>
列表。
<ItemGroup>
<PublishReadyToRunExclude Include="Contoso.Example.dll" />
</ItemGroup>
如何选择预编译的方法集?
编译器将尝试预编译尽可能多的方法。 但是,出于各种原因,预计使用 ReadyToRun 功能不会阻止 JIT 的执行。 此类原因可能包括,但包括但不限于:
- 使用在单独的程序集中定义的泛型类型。
- 与本机代码互操作。
- 使用编译器无法证明可在目标计算机上安全使用的硬件内部函数。
- 某些不寻常的 IL 模式。
- 通过反射或 LINQ 创建动态方法。
生成用于分析器的符号
使用 ReadyToRun 编译应用程序时,探查器可能需要符号来检查生成的 ReadyToRun 文件。 若要启用符号生成,请指定 <PublishReadyToRunEmitSymbols>
属性。
<PropertyGroup>
<PublishReadyToRunEmitSymbols>true</PublishReadyToRunEmitSymbols>
</PropertyGroup>
这些符号将放置在发布目录中,对于 Windows,文件扩展名为 .ni.pdb,对于 Linux,文件扩展名为 .r2rmap。 这些文件通常不会重新分发给最终客户,而是通常存储在符号服务器中。 通常,这些符号对于调试与启动应用程序相关的性能问题很有用,因为 分层编译 会将 ReadyToRun 生成的代码替换为动态生成的代码。 但是,如果尝试分析禁用 分层编译 的应用程序,符号将很有用。
复合 ReadyToRun
常规 ReadyToRun 编译会生成可单独处理和操作的二进制文件。 从 .NET 6 开始,添加了对复合 ReadyToRun 编译的支持。 复合 ReadyToRun 会编译一组必须同时分发的程序集。 这的优点是编译器能够执行更好的优化,并减少无法通过 ReadyToRun 进程编译的方法集。 但是,作为权衡,编译速度会显著减少,应用程序的整体文件大小会显著增加。 由于这些缺点,因此仅建议将复合 ReadyToRun 用于禁用分层编译的应用程序,或者用于在 Linux 上运行且通过自包含部署寻求最佳启动时间的应用程序。 若要启用复合 ReadyToRun 编译,请指定 <PublishReadyToRunComposite>
属性。
<PropertyGroup>
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
</PropertyGroup>
注释
在 .NET 6 中,仅独立部署支持复合 ReadyToRun。
跨平台/体系结构限制
对于某些 SDK 平台,ReadyToRun 编译器能够针对其他目标平台进行交叉编译。
在面向 .NET 6 及更高版本时,下表描述了支持的编译目标。
SDK 平台 | 支持的目标平台 |
---|---|
Windows X64 | Windows(X86、X64、Arm64)、Linux(X64、Arm32、Arm64)、macOS(X64、Arm64) |
Windows X86 | Windows (X86)、Linux (Arm32) |
Linux X64 | Linux(X64、Arm32、Arm64)、macOS(X64、Arm64) |
Linux Arm32 | Linux Arm32 |
Linux Arm64 | Linux(X64、Arm32、Arm64)、macOS(X64、Arm64) |
macOS X64 | Linux(X64、Arm32、Arm64)、macOS(X64、Arm64) |
macOS Arm64 | Linux(X64、Arm32、Arm64)、macOS(X64、Arm64) |
在面向 .NET 5 及更低版本时,下表描述了支持的编译目标。
SDK 平台 | 支持的目标平台 |
---|---|
Windows X64 | Windows X86、Windows X64、Windows Arm64 |
Windows X86 | Windows X86、Windows Arm32 |
Linux X64 | Linux X86、Linux X64、Linux Arm32、Linux Arm64 |
Linux Arm32 | Linux Arm32 |
Linux Arm64 | Linux Arm64 |
macOS X64 | macOS X64 |