ReadyToRun 编译

可以通过将应用程序集编译为 ReadyToRun (R2R) 格式来改进 .NET Core 应用程序的启动时间和延迟。 R2R 是一种预先 (AOT) 编译形式。

R2R 二进制文件通过减少应用程序加载时实时 (JIT) 编译器需要执行的工作量来改进启动性能。 二进制文件包含与 JIT 将生成的内容类似的本机代码。 但是,R2R 二进制文件更大,因为它们包含中间语言 (IL) 代码(某些情况下仍需要此代码)和相同代码的本机版本。 仅当发布面向特定运行时环境 (RID)(如 Linux x64 或 Windows x64)的应用时 R2R 才可用。

若要将项目编译为 ReadyToRun,必须在将 PublishReadyToRun 属性设置为 true 时发布应用程序。

可以通过两种方式将应用发布为 ReadyToRun:

  1. 向 dotnet publish 命令直接指定 PublishReadyToRun 标志。 有关详细信息,请参阅 dotnet publish

    dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true
    
  2. 在项目中指定属性。

    • 向项目中添加 <PublishReadyToRun> 设置。
    <PropertyGroup>
      <PublishReadyToRun>true</PublishReadyToRun>
    </PropertyGroup>
    
    • 在不使用任何特殊参数的情况下发布应用程序。
    dotnet publish -c Release -r win-x64
    

使用 ReadyToRun 功能的影响

预先编译会对应用程序性能产生复杂的性能影响,这种影响可能很难预测。 通常情况下,程序集的大小将增长到两到三倍。 这种文件物理大小的增加可能会降低从磁盘加载程序集的性能,并增加进程的工作集。 但相对地,在运行时编译的方法数通常会大幅减少。 因此,启用 ReadyToRun,包含大量代码的大多数应用程序都会获得很大的性能增益。 由于 .NET 运行时库已使用 ReadyToRun 进行预编译,因此启用 ReadyToRun 时,具有少量代码的应用程序很可能不会获得显著改进。

这里讨论的启动改进不仅适用于应用程序启动,还适用于在应用程序中首次使用任何代码。 例如,ReadyToRun 可用于降低在 ASP.NET 应用程序中首次使用 Web API 时的响应延迟。

通过分层编译进行交互

预先生成的代码优化程度不如 JIT 生成的代码。 为了解决此问题,分层编译将使用 JIT 生成的方法替换常用的 ReadyToRun 方法。

如何选择预编译的一组程序集?

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