ThinLTO¶
简介¶
ThinLTO 编译是一种新型的 LTO,它既可扩展又可增量。LTO(链接时优化)通过全程序分析和跨模块优化来实现更好的运行时性能。但是,单片 LTO 通过将所有输入合并到一个模块中来实现这一点,这在时间或内存方面不可扩展,并且还阻止了快速增量编译。
在 ThinLTO 模式下,与常规 LTO 一样,clang 在编译阶段后发出 LLVM 位码。ThinLTO 位码通过模块的简要摘要进行增强。在链接步骤中,仅读取摘要并将其合并到组合摘要索引中,该索引包括函数位置的索引,以便在以后进行跨模块函数导入。然后对组合摘要索引执行快速有效的全程序分析。
但是,所有转换(包括函数导入)都在以后模块在完全并行后端中优化时发生。默认情况下,链接器 支持 ThinLTO 被设置为在线程中启动 ThinLTO 后端。因此,使用模型不受影响,因为快速串行薄链接步骤和后端之间的区别对用户来说是透明的。
有关 ThinLTO 设计和当前性能的更多信息,请参见 LLVM 博客文章 ThinLTO:可扩展和增量 LTO。虽然调整仍在进行中,但博客文章中的结果表明,ThinLTO 与 LTO 相比已经表现良好,在许多情况下都与性能改进相匹配。
当前状态¶
Clang/LLVM¶
clang 的 3.9 版本包含 ThinLTO 支持。但是,ThinLTO 正在积极开发中,并且正在为下一个版本添加新功能、改进和错误修复。有关最新的 ThinLTO 支持,请构建最新版本的 clang 和 LLVM。
链接器¶
目前,ThinLTO 支持以下链接器
gold(通过 gold-plugin):与单片 LTO 类似,这需要使用已配置为启用插件的 gold 链接器。
ld64:从Xcode 8 开始。
lld:从 r284050 开始支持 ELF,从 r298942 开始支持 COFF。
用法¶
基础¶
要利用 ThinLTO,只需将 -flto=thin 选项添加到编译和链接中。例如
% clang -flto=thin -O2 file1.c file2.c -c
% clang -flto=thin -O2 file1.o file2.o -o a.out
使用 lld-link 时,仅需将 -flto 选项添加到编译步骤中
% clang-cl -flto=thin -O2 -c file1.c file2.c
% lld-link /out:a.exe file1.obj file2.obj
如前所述,默认情况下,链接器将并行启动 ThinLTO 后端线程,并将生成的本机目标文件传递回链接器以进行最终本机链接。因此,使用模型与非 LTO 相同。
使用 gold 时,如果在链接过程中看到以下形式的错误
/usr/bin/ld: error: /path/to/clang/bin/../lib/LLVMgold.so: could not load plugin library: /path/to/clang/bin/../lib/LLVMgold.so: cannot open shared object file: No such file or directory
那么要么 gold 未配置为启用插件,要么 clang 未在构建时正确设置 -DLLVM_BINUTILS_INCDIR
。请参阅LLVM gold 插件 的说明。
控制后端并行度¶
默认情况下,ThinLTO 链接步骤将并行启动与内核数一样多的线程。如果无法为体系结构计算内核数,那么它将并行启动 std::thread::hardware_concurrency
个线程。对于支持超线程的机器,这是虚拟内核的总数。对于某些应用程序和机器配置,这可能过于激进,在这种情况下,可以通过以下方式将并行度减少到 N
:
gold:
-Wl,-plugin-opt,jobs=N
ld64:
-Wl,-mllvm,-threads=N
ld.lld、ld64.lld:
-Wl,--thinlto-jobs=N
lld-link:
/opt:lldltojobs=N
N
的其他可能值是
0:每个物理内核使用一个线程(默认)
1:仅使用一个线程(禁用多线程)
all:每个逻辑内核使用一个线程(使用所有超线程)
增量¶
ThinLTO 通过使用缓存来支持快速增量构建,该缓存当前必须通过链接器选项启用。
gold(从 LLVM 4.0 开始):
-Wl,-plugin-opt,cache-dir=/path/to/cache
ld64(从 clang 3.9 和 Xcode 8 开始支持)和 Mach-O ld64.lld(从 LLVM 15.0 开始):
-Wl,-cache_path_lto,/path/to/cache
ELF ld.lld(从 LLVM 5.0 开始):
-Wl,--thinlto-cache-dir=/path/to/cache
COFF lld-link(从 LLVM 6.0 开始):
/lldltocache:/path/to/cache
缓存修剪¶
为了帮助控制缓存的大小,ThinLTO 支持缓存修剪。缓存修剪在 gold、ld64 和 lld 中受支持,但目前只有 gold 和 lld 允许您使用策略字符串来控制策略。缓存策略必须使用链接器选项指定。
gold(从 LLVM 6.0 开始):
-Wl,-plugin-opt,cache-policy=POLICY
ELF ld.lld(从 LLVM 5.0 开始)、Mach-O ld64.lld(从 LLVM 15.0 开始):
-Wl,--thinlto-cache-policy=POLICY
COFF lld-link(从 LLVM 6.0 开始):
/lldltocachepolicy:POLICY
策略字符串是一系列由 :
字符分隔的键值对。可能的键值对是
cache_size=X%
:缓存目录的最大大小是磁盘可用空间的X
%。设置为 100 表示没有限制,设置为 50 表示缓存大小不会超过磁盘可用空间的一半。大于 100 的值无效。值为 0 表示禁用基于百分比大小的修剪。默认值为 75%。cache_size_bytes=X
、cache_size_bytes=Xk
、cache_size_bytes=Xm
、cache_size_bytes=Xg
:将缓存目录的最大大小设置为X
字节(或 KB、MB、GB 分别)。超过磁盘可用空间的值将减少到磁盘可用空间的值。值为 0 表示禁用基于字节大小的修剪。默认情况下没有基于字节大小的修剪。请注意,ThinLTO 将同时应用两种基于大小的修剪策略,更改一种策略不会影响另一种策略。例如,仅
cache_size_bytes=1g
的策略会导致应用 1GB 和默认的 75% 策略,除非覆盖了默认的cache_size
。cache_size_files=X
:设置缓存目录中的最大文件数。设置为 0 表示没有限制。默认值为 1000000 个文件。prune_after=Xs
、prune_after=Xm
、prune_after=Xh
:将缓存文件的过期时间设置为X
秒(或分钟、小时分别)。当文件未被访问prune_after
秒时,它将从缓存中删除。值为 0 表示禁用基于过期时间的修剪。默认值为 1 周。prune_interval=Xs
、prune_interval=Xm
、prune_interval=Xh
:将修剪间隔设置为X
秒(或分钟、小时分别)。这旨在用于避免过于频繁地扫描目录。它不会影响修剪哪些文件的决定。值为 0 表示强制进行扫描。默认值为每 20 分钟。
Clang 自举¶
要使用 ThinLTO 自举 clang/LLVM,请按照以下步骤操作
-DLLVM_ENABLE_LTO=Thin
-DCMAKE_C_COMPILER=/path/to/host/clang
-DCMAKE_CXX_COMPILER=/path/to/host/clang++
-DCMAKE_RANLIB=/path/to/host/llvm-ranlib
-DCMAKE_AR=/path/to/host/llvm-ar
或者,在 Windows 上
-DLLVM_ENABLE_LTO=Thin
-DCMAKE_C_COMPILER=/path/to/host/clang-cl.exe
-DCMAKE_CXX_COMPILER=/path/to/host/clang-cl.exe
-DCMAKE_LINKER=/path/to/host/lld-link.exe
-DCMAKE_RANLIB=/path/to/host/llvm-ranlib.exe
-DCMAKE_AR=/path/to/host/llvm-ar.exe
要使用其他链接器参数来控制后端并行度 或启用自举编译器的增量 构建,在配置构建后,修改构建目录中生成的 CMakeCache.txt 文件。在
CMAKE_EXE_LINKER_FLAGS:STRING=
之后指定任何其他链接器选项。请注意,如果在前面的步骤中直接指定了链接器插件选项,则配置可能会失败。
BOOTSTRAP_LLVM_ENABLE_LTO=Thin
将为阶段 2 和阶段 3 启用 ThinLTO,以防用于阶段 1 的编译器不支持 ThinLTO 选项。
更多信息¶
来自 LLVM 项目博客:ThinLTO:可扩展和增量 LTO