AnyMath 文档

系统映像构建

构建Julia系统映像

朱莉娅船上有一个准备好的系统图像,其中包含 基地 模块,命名 系统。纪. 这个文件也被预编译成一个名为共享库 系统。{so,dll,dylib} 在尽可能多的平台上,从而大大提高启动时间。 在没有预编译系统映像文件的系统上,可以从Julia的源文件中生成一个 DATAROOTDIR/朱莉娅/基地 文件夹。

Julia默认会在一半的可用系统线程上生成其系统映像。 这可能由 JULIA_IMAGE_THREADS环境变量。

由于多种原因,此操作非常有用。 用户可以:

*在未附带预编译共享库系统映像的平台上构建预编译共享库系统映像,从而缩短启动时间。 *修改 基地,重建系统映像并使用新的 基地 下次茱莉亚开始。 *包括一个 userimg的。jl 将软件包包含到系统映像中的文件,从而创建具有嵌入到启动环境中的软件包的系统映像。

该https://github.com/JuliaLang/PackageCompiler.jl[脧锚脧赂`PackageCompiler。jl` 包]包含方便的包装功能,以自动化此过程。

针对多个微体系结构优化的系统图像

系统映像可以在同一指令集架构(ISA)下针对多个CPU微架构同时编译。 为了利用不同的ISA扩展或其他微体系结构特性,可以创建同一函数的多个版本,并将最小分派点插入到共享函数中。 提供最佳性能的版本将在运行时根据可用的CPU功能自动选择。

指定多个系统映像目标

通过在系统映像编译期间传递多个目标,可以启用多微体系结构系统映像。 这可以用 JULIA_CPU_目标作出选择或与 -C 手动运行编译命令时的命令行选项。 多个目标由 ; 在选项字符串中。 每个目标的语法是一个CPU名称,后跟多个功能,由 ,. LLVM支持的所有功能都受支持,并且可以使用 - 前缀。 (+ prefix也被允许和忽略,以与LLVM语法一致)。 此外,还支持一些特殊功能来控制函数克隆行为。

请注意,最好指定 clone_all底座(<n>) 除了第一个以外的每一个目标。 这使得明确哪些目标具有克隆的所有功能,以及哪些目标基于其他目标。 如果不这样做,默认行为是不克隆每个函数,并在不克隆函数时使用第一个目标的函数定义作为后备。

  1. clone_all 默认情况下,只有最有可能从微体系结构特性中受益的函数才会被克隆。 何时 clone_all 是为目标指定的,但是,系统映像中的*所有*函数将为目标克隆。 否定形式 -clone_all 可用于防止内置启发式克隆所有函数。

  2. 底座(<n>) 哪里 <n> 是非负数的占位符(例如 底座(0), 基地(1)). 默认情况下,部分克隆(即不 clone_all)如果未克隆函数,target将使用默认目标(指定的第一个目标)中的函数。 此行为可以通过使用 底座(<n>) 选择。 该 nth目标(基于0)将用作基本目标,而不是默认目标(0th)一个。 基本目标必须是 0 或其他 clone_all 目标。 指定非-clone_all 目标作为基础目标会导致错误。

  3. 选择大小 当运行时性能没有显着影响时,这会导致目标的函数针对大小进行优化。 这对应于 -操作系统 GCC和Clang选项。

  4. 最小尺寸 这会导致针对可能会对运行时性能产生重大影响的大小优化目标函数。 这对应于 -奥兹 叮当选项。

作为一个例子,在撰写本文时,以下字符串用于创建官方 x86_64 Julia二进制文件可从julialang.org:

generic;sandybridge,-xsaveopt,clone_all;haswell,-rdrnd,base(1)

这将创建一个具有三个独立目标的系统映像;一个用于通用 x86_64 处理器,一个带有 桑迪布里奇 ISA(明确排除 [医]xsaveopt)显式克隆所有函数,一个针对 哈斯韦尔 ISA,基于 桑迪布里奇 sysimg版本,也不包括 rdrnd. 当Julia实现加载生成的sysimg时,它将检查主机处理器是否匹配CPU能力标志,从而启用尽可能高的ISA级别。 请注意,基本级别(通用型)需要 cx16 指令,该指令在某些虚拟化软件中被禁用,并且必须为 通用型 待加载目标。 或者,可以使用目标生成sysimg 通用,-cx16 为了更好的兼容性,但是请注意,这可能会导致某些代码的性能和稳定性问题。

实施概述

这是实施中涉及的不同部分的简要概述。 有关更多实现细节,请参阅每个组件的代码注释。

  1. 系统映像编译 解析和克隆决策在 src/处理器*. 我们目前支持基于循环,simd指令或其他数学运算(例如fastmath,fma,muladd)的存在克隆函数。 此信息传递给 src/llvm-多版本控制。cpp 这是真正的克隆。 除了做克隆和插入调度槽(见 多版本控制::runOnModule 对于这是如何完成的),传递还生成元数据,以便运行时可以正确加载和初始化系统映像。 元数据的详细描述可在 src/处理器。h.

  2. 系统图像加载 系统映像的加载和初始化是在 src/处理器* 通过解析在系统映像生成期间保存的元数据。 主机特征检测和选择决策在 src/processor_*。cpp 取决于ISA。 目标选择将更喜欢精确的CPU名称匹配、更大的矢量寄存器大小和更多的特性。 这个过程的概述是在 src/处理器。cpp.

修剪;修剪

系统映像通常相当大,因为Base包含许多功能,并且默认情况下,系统映像还包括几个软件包,如LinearAlgebra,以便于方便和向后兼容性。 大多数程序将只使用这些包中的一小部分功能。 因此,构建排除未使用的函数以节省空间的二进制文件是有意义的,称为"修剪"。

虽然修剪的基本思想是声音,但Julia具有动态和反射功能,使得很难(或不可能)总体上知道哪些功能未使用。 作为一个极端的例子,考虑如下代码

getglobal(Base, Symbol(readchomp(stdin)))(1)

此代码从读取函数名 标准普尔 并根据值从基础调用命名函数 1. 在这种情况下,无法预测哪个函数将被调用,因此没有任何函数可以可靠地被认为是"未使用的"。 除了一些值得注意的例外(Julia自己的REPL就是其中之一),大多数现实世界的程序都不会做这样的事情。

例如,当存在类型不稳定性使编译器无法预测将调用哪个方法时,就会出现不太极端的情况。 但是,如果代码类型良好并且不使用反射,则可以确定一组完整的(希望)相对较小的所需方法,其余的可以删除。 该 --修剪 命令行选项请求这种编译。

何时 --修剪 在用于构建系统映像的命令中指定,编译器从使用以下方法标记的方法开始跟踪调用 基地。实验性的。入口点. 如果调用过于动态而无法合理地缩小可能的调用目标,则在编译时会给出一个错误,显示调用的位置。 出于测试目的,可以通过指定跳过这些错误 --修剪=不安全--修剪=不安全-警告. 然后你会得到一个系统映像构建,但如果需要的代码不存在,它可能会在运行时崩溃。

它通常是有意义的指定 --条带-ir 随着 --修剪,由于修剪的二进制文件是完全编译的,因此不需要Julia IR。 在某个时候我们可能会 --修剪 暗示 --条带-ir,但现在我们已经保持它们正交。

为了获得尽可能小的二进制文件,它也将有助于指定 --条带-元数据 并运行Unix 条带 效用。 但是,这些步骤分别删除了特定于Julia和本机(DWARF格式)的调试信息,因此将使调试更加困难。

常见问题

*基础全局变量 标准普尔, 标准输出,而 斯德尔 是非常数,因此它们的类型是未知的。 所有打印都应使用具有已知类型的特定IO对象。 最简单的替代是使用 打印(核心。stdout,x) 而不是 印刷(x)打印(stdout,x). *使用工具,如https://github.com/aviatesk/JET.jl[JET.jl],https://github.com/JuliaDebug/Cthulhu.jl[Cthulhu.jl],和/或https://github.com/timholy/SnoopCompile.jl[SnoopCompile]识别类型推断的失败,并按照我们的 性能提示来修复它们。

兼容性问题

我们已经确定了许多对Base的微小更改,这些更改显着增加了可以可靠修剪的程序集。 不幸的是,其中一些更改将被视为中断,因此仅在请求修剪时应用(这是由外部构建脚本完成的,目前在测试套件中维护为 contrib/juliac/juliac-buildscript。jl). 因此,在许多情况下,修剪将要求您选择使用Base和一些标准库的新变体。

如果要使用修剪,设置持续集成测试非常重要,该测试执行修剪的构建并完全测试生成的程序。 幸运的是,如果你的程序成功地编译 --修剪 那么它很可能像以前一样工作。 但是,需要CI来确保您的程序在开发时继续使用修剪来构建。

包作者可能希望测试他们的包是否"修剪安全",但这通常是不可能的。 修剪仅在给定具体入口点的情况下工作,例如 主要() 图书馆入口点是从茱莉亚外部调用的。 对于泛型包,现有的类型稳定性测试如下 @推断喷气式飞机。@报告_call 是尽可能接近你可以检查修剪兼容性.

修剪还会在julia的次要版本之间引入新的兼容性问题。 此时,我们无法保证可以在Julia的一个版本中修剪的程序也可以在Julia的所有未来版本中修剪。 然而,这种破损预计是罕见的。 我们还计划尝试_increase_随着时间的推移可以修剪的程序集。