Engee 文档

执行前编译

该页面正在翻译中。

本文档描述了Julia中ahead-of-time(aot)编译系统的设计和结构。 创建系统映像和包映像时使用此系统。 这里描述的大部分实现过程都在files的aotcompile中。cpp`,'staticdata。c’和’处理器。cpp'。

导言

虽然JIT编译通常在Julia中执行,但代码可以在执行之前预先编译,并将结果保存到文件中。 这可能是有用的原因有很多。:

  1. 以减少启动Julia过程所需的时间。

  2. 以减少在JIT编译器中花费的时间,而不是执行代码(首次执行时间,TTFX)。

  3. 以减少JIT编译器使用的内存量。

一般概览

下面提供了有关完整过程的当前实现的信息,该过程在用户编译新的AOT模块时在内部执行,例如,当他输入`using Foo’时。 最有可能的是,随着引入更高效的处理方法,这些信息将随时间而改变,因此当前的实现可能不完全匹配下面描述的数据流和功能。

代码图像的编译

首先,有必要定义应该编译成机器代码的方法。 这只能在编译代码实际运行时完成,因为需要编译的方法集取决于传递给方法的参数类型,并且具有某些类型组合的方法调用可能直到运行时才知 在此过程中,将跟踪编译器看到的确切方法以进行后续编译,从而创建编译跟踪。

目前,在编译映像时,Julia在不同的进程中开始跟踪生成,而不是在执行AOT编译的进程中。 这可能会影响预编译期间使用调试器的尝试。 要使用调试器调试预编译,最好使用rr调试器,记录整个过程树,使用`rr ps’确定相应的失败过程,然后使用`rr replay-p PID`仅重现失败过程。

定义要编译的方法后,传递’jl_create_system_image’函数。 此函数设置将机器代码序列化到文件时要使用的许多数据结构,然后使用方法数组调用`jl_create_native`。 'jl_create_native’为方法执行代码生成(codegen),创建一个或多个LLVM模块。 'jl_create_system_image’然后记录有关从模块生成代码时创建的内容的有用信息。

之后,模块与`jl_create_system_image`函数记录的信息一起传递给`jl_dump_native`函数。 'jl_dump_native’包含将模块序列化为位码,对象或程序集文件所需的代码,具体取决于传递给Julia的命令行参数。 然后将序列化的代码和信息作为存档写入文件。

最后一步是为使用’jl_dump_native’创建的存档中的目标文件运行系统链接器。 在此步骤结束时,将创建包含已编译代码的共享库。

上传代码图像

加载代码映像时,链接器创建的共享库被加载到内存中。 然后从共享库中加载系统映像数据。 此数据包含有关已编译到共享库中的类型、方法和代码实例的信息。 这些数据用于将运行时环境的状态还原到编译代码映像时的状态。

如果代码映像是在启用了多个版本控制的情况下编译的,则加载程序将根据当前计算机上可用的处理器功能选择每个函数的适当版本。

对于系统映像:由于没有加载其他代码,运行时环境的状态现在与编译代码映像时的状态相同。 对于包映像,环境的状态可能与代码编译时的状态不同,因此应使用全局方法表检查每个方法的正确性。

方法的汇编

跟踪编译的方法

Julia有一个命令行标志,用于编写由JIT编译器编译的所有方法--'--trace-compile=filename'。 当一个函数被编译并且这个标志包含文件名时,Julia将输出一个预编译报告给这个文件,指出调用它的方法和参数的类型。 这将创建一个预编译脚本,可在稍后的aot编译过程中使用。 在包https://julialang …​github.io/PrecompileTools.jl/stable /[PrecompileTools]包含为开发人员简化此过程的工具。

jl_create_system_image

`jl_create_system_image’保存后续恢复运行时状态所需的所有Julia特定元数据。 这包括代码实例、方法实例、方法表和类型信息等数据。 此函数还定义了将机器代码序列化为文件所需的数据结构。 最后,它调用’jl_create_native’来创建一个或多个包含传递方法的机器代码的LLVM模块。 `Jl_create_native’函数负责为传递给它的方法生成代码(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_TARGET'并在优化管道中间接执行多个版本控制传递。 为了使用多线程,在将模块拆分为在其自己的线程中创建的子模块之前,添加了一个注释步骤,该步骤使用整个模块中可用的信息来决定哪些函数应 注释后,单独的线程可以并行生成不同体系结构的代码,因为保证另一个子模块创建将被克隆函数调用的必要函数。

存档还包含有关模块序列化方式的一些其他元数据,例如用于序列化模块的线程数和编译的函数数。

静态布局

AOT编译过程的最后一步是为使用`jl_dump_native’创建的存档中的对象文件运行链接器。 因此,将创建一个包含已编译代码的共享库。 然后可以将此共享库加载到Julia中以恢复运行时环境的状态。 编译系统映像时,C编译器使用的本机链接器会创建最终的共享库。 对于包映像,LLVM链接器LLD提供了更一致的布局接口。

上传代码图像

上传共享库

上传代码映像的第一步是下载链接器创建的共享库。 为此,请在共享库的路径中调用`jl_dlopen`函数。 此函数加载共享库并解析其中的所有符号。

装载机器代码

首先,加载器需要确定编译后的机器码是否适合加载器的执行架构。 这对于避免执行旧处理器无法识别的指令是必要的。 为此,请将当前计算机上可用的CPU功能与编译代码的CPU功能进行比较。 如果启用了多个版本控制,加载程序将根据当前计算机上可用的处理器功能选择每个功能的适当版本。 如果多个版本控制没有复盖任何功能集,则加载程序将返回错误。

在多个版本控制过程中,将创建所有模块函数的多个全局数组。 如果进程是多线程的,则会创建一个数组数组,加载程序将其重新排列为一个大数组,其中包含为此体系结构编译的所有函数。 全局模块变量也会发生类似的过程。

茱莉亚的财富酊

然后,加载器使用加载机器代码产生的全局变量和函数来配置当前进程中Julia运行时的基本数据结构。 在此过程中,类型和方法被添加到Julia运行时,并且缓存的机器代码变得可供其他Julia函数和解释器使用。 对于包映像:必须检查每个方法,因为全局方法表的状态必须与编译包映像的状态相对应。 特别是,如果在下载和编译包映像期间有不同的方法集,那么应该取消该方法并在第一次使用时重新编译它。 这是必要的,以确保执行语义保持不变,无论是预编译包还是直接执行代码。 系统映像不需要执行此检查,因为全局方法表在引导时为空。 这样,系统映像的加载速度比包映像快。