1. 关于 LTO 、-flto 、ThinLTO
- LTO(Link Time Optimization)链接时优化是链接期间的程序优化,多个中间文件通过链接器合并在一起,并将它们组合为一个程序,缩减代码体积,因此链接时优化是对整个程序的分析和跨模块的优化。
link time时需要为GP alias计算大小,是否超过16bit,以决定用什么东西。该计算在linker中做而不是compiler来做。 - flto是使用lto的主要方法,是一个优化选项,禁用lto使用-fno-lto。flto主要做的操作有inline、ipa(ipo)和alias分析等。
- ThinLTO是一种可扩展和增量式的新型LTO,与LTO相比,表现甚至更好。要使用ThinLTO,只需添加-flto=thin选项即可进行编译和链接。第一阶段类似于传统LTO的步骤,在进行一些早期优化(主要是为了减小大小)之后,调用前端将每个输入源文件转换为包含IR的中间文件。只是使用ThinLTO,每个文件中都包含一个附加的摘要部分。ThinLTO overview如下:
2. LLVM或AOCC中flto
LLVM中lto work在IR(Intermediate representation)上,我们常用的选项-flto其实代表-flto=full,指lto将分散的目标文件的所有LLVM IR组合到一个大的LLVM模块中,然后对其进行整体分析优化并生成machinecode,该选项仅并行执行前端的语义分析,优化和machinecode生成在单线程完成。-flto=thin则是把模块分开,根据需要才从其他模块导入功能,并且除全局分析外均采用并行的方式进行优化和machinecode的生成。因此使用-flto=thin比-flto=full编译链接的速度大大加快,而且对于SPEC CPU2017部分benchmark性能更好。
2.1 Linkers
ThinLTO当前在LLVM编译器中实现,其思想是主要是指导inline优化。它支持以下三种链接器:
- gold linker:使用前需要先安装插件,然后就可以使用-fuse-ld=gold。安装请见(The LLVM gold plugin)。
- ld64:从Xcode 8开始。
- lld:来自于llvm项目,运行速度更快,特别是在众核处理器上。默认支持LTO,lld读取llvm IR bitcode进行编译优化并输出文件。详细请见(The LLVM Linker — lld)。
基本用法:
// 使用-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
- 备注:在AOCC2.0或更高版本中已不再使用gold linker plugins作为链接器,而是使用lld作为default linker。在LLVM9.0或更高版本中使用-fuse-ld=lld指定链接器。
3. GCC和ICC中flto
3.1 GCC中用法
GCC中使用-flto编译代码时,它将生成GIMPLE中间表示,并将其写到目标文件的ELF(部分的数据结构和枚举代码在 lto-streamer.h中),将目标文件链接在一起时,将从这些ELF中读取所有功能体,并将其实例化。当前,大多数基于ELF的系统以及darwin,cygwin和mingw系统都启用了LTO支持。
从源码时使用-flto编译,所有的passes在all_lto_gen_passes中管理,它包含两个IPA passes:
- pass_ipa_lto_gimple_out:该pass执行函数体lto_output在lto-streamer-out.c文件中。它遍历调用图,对每个可达的声明,类型和函数进行编码。
- pass_ipa_lto_finish_out:该pass执行函数体produce_asm_for_decls在lto-streamer-out.c文件中。它获取上一步中的结果并将其编码在相应的ELF文件节中。
GCC中基本用法:
gcc -c -o test1.o -O2 -flto test1.c
gcc -O2 -flto test1.o test2.o ... -o test
// LTO的另一个功能是可以对用不同语言编写的文件进行过程间优化
gcc -c -flto foo.c
g++ -c -flto bar.cc
gfortran -c -flto baz.f90
g++ -o myprog -flto -O2 foo.o bar.o baz.o -lgfortran
GCC中如果你想知道你的代码从前端开始的编译过程中做了哪些phase,可使用fdump-tree-all打印出来,如下:
gcc -flto -O3 ..... -fdump-tree-all test.c // 可使用任意选项,此处打印flto 和O3
由上图可看出,源码经过前端处理之后在中端需要经过很多处理,你可以结合gdb看到每一个处理过程的转换信息。
对于最简单的 printf(“hello world”),在GCC中没有flto选项的情况下得到的汇编码如下:
使用flto之后的大致输出信息如下:
由此看出:LTO输出不包含目标代码,而仅包含LTO信息。
关于flto优化的具体信息可通过verbose打印出来:
在以上标注的6个步骤中,-flto真正开始起作用是从第4步开始,lto1位置。
3.2 ICC中用法
ICC中提供了使用-ffat-lto-objects选项的链接时优化,以实现GCC兼容性。ICC中的链接时优化是在IPO中选取启用的。
ICC中基本用法:
ifort -c -o test1.o -O2 -ipo test1.f90
ifort -O2 -ipo test1.o test2.o ... -o test
4. 注意点
- LTO生成的.o文件并不是真正的Object file,而是一个携带优化信息的中间文件,这些.o文件通过Linker整理分析之后合并成最终可执行文件。
- 使用LTO会导致编译链接速度变慢,占用更大的内存空间。
- 一些选项不支持和LTO一起使用,例如:-flive-patching等。甚至配合某些选项使用时一些benchmark编不过。