提前编译
本文档描述了Julia中ahead-of-time(aot)编译系统的设计和结构。 生成系统映像和包映像时使用此系统。 这里描述的大部分实现位于 aotcompile。cpp, 静态数据。c,而 处理器。cpp
导言
虽然Julia通常编译代码实时(JIT),但可以提前编译代码并将生成的代码保存到文件中。 这可能是有用的原因有很多:
-
以减少启动Julia进程所需的时间。
-
以减少在JIT编译器中花费的时间,而不是执行代码(首次执行时间,TTFX)。
-
以减少JIT编译器使用的内存量。
高级别概览
以下描述是在用户编译新AOT模块时在内部发生的端到端管道的当前实现细节的快照,例如在他们键入时发生 使用Foo. 随着我们实现更好的处理方法,这些细节可能会随着时间的推移而改变,因此当前的实现可能与下面描述的数据流和函数不完全匹配。
编译代码图像
首先,必须识别需要编译为本机代码的方法。 这只能通过实际执行要编译的代码来完成,因为需要编译的方法集取决于传递给方法的参数的类型,并且具有某些类型组合的方法调用可能在运行时才知道。 在此过程中,将跟踪编译器看到的确切方法以供以后编译,从而生成编译跟踪。
|
注意当前在编译映像时,Julia在与执行AOT编译的过程不同的过程中运行跟踪生成。 在预编译期间尝试使用调试器时,这可能会产生影响。 使用调试器调试预编译的最佳方法是使用rr调试器,记录整个进程树,使用 |
一旦确定了要编译的方法,它们就会传递给 jl_create_system_image 函数。 此函数设置将在将本机代码序列化到文件时使用的许多数据结构,然后调用 [医]创造性 与方法的阵列。 [医]创造性 在方法上运行codegen会生成一个或多个LLVM模块。 jl_create_system_image 然后记录一些有用的信息,说明代码从模块中产生了什么。
然后将模块传递给 jl_dump_native,连同由 jl_create_system_image. jl_dump_native 包含将模块序列化为位码、对象或程序集文件所需的代码,具体取决于传递给Julia的命令行选项。 然后将序列化的代码和信息作为存档写入文件。
最后一步是在由 jl_dump_native. 完成此步骤后,将生成包含已编译代码的共享库。
编译方法
跟踪编译的方法
Julia有一个命令行标志来记录由JIT编译器编译的所有方法, --跟踪-编译=文件名. 当一个函数被编译并且这个标志有一个文件名时,Julia将用它被调用的方法和参数类型向该文件打印一个预编译语句。 因此,这会生成一个预编译脚本,可在稍后的aot编译过程中使用。 该https://julialang.github.io/PrecompileTools.jl/stable/[PrecompileTools]软件包具有工具,可以使软件包开发人员更容易利用此功能。
jl_create_system_image
jl_create_system_image 保存以后恢复运行时状态所需的所有Julia特定元数据。 这包括代码实例、方法实例、方法表和类型信息等数据。 此函数还设置将本机代码序列化为文件所需的数据结构。 最后,它调用 [医]创造性 创建一个或多个LLVM模块,其中包含传递给它的方法的本机代码。 [医]创造性 负责在传递给它的方法上运行codegen。
jl_dump_native
jl_dump_native 负责将包含本机代码的LLVM模块序列化为文件。 除模块,所产生的系统图像数据 jl_create_system_image 被编译为全局变量。 此方法的输出是包含代码和系统映像数据的位码、对象和/或程序集存档。
jl_dump_native 通常是发射本机代码时较大的时间接收器之一,大部分时间都花在优化LLVM IR和发射机器代码上。 因此,该函数能够对优化和机器代码发射步骤进行多线程处理。 此多线程是根据模块的大小参数化的,但可以通过设置 JULIA_IMAGE_THREADS环境变量。 默认的最大线程数是可用线程数的一半,但将其设置为较低可以减少编译期间的峰值内存使用。
jl_dump_native 当与Julia加载器集成时,还可以生成针对多个体系结构优化的本机代码。 这是通过设置触发 JULIA_CPU_目标环境变量,由优化管道中的多版本控制传递介导。 为了使其与多线程一起工作,在将模块拆分为在其自己的线程上发出的子模块之前,添加了一个注释步骤,并且此注释步骤使用整个模块中可用的信息来决定为不同的体系结构克隆哪些函数。 一旦注释发生,单个线程就可以并行地为不同的体系结构发出代码,因为知道不同的子模块保证会产生将被克隆函数调用的必要函数。
有关模块序列化方式的其他一些元数据也存储在存档中,例如用于序列化模块的线程数和编译的函数数。