编译优化之 - 链接时优化(LTO)入门

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如下:

编译优化之 - 链接时优化(LTO)入门_第1张图片

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优化。它支持以下三种链接器:

  1. gold linker:使用前需要先安装插件,然后就可以使用-fuse-ld=gold。安装请见(The LLVM gold plugin)。
  2. ld64:从Xcode 8开始。
  3. 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的系统以及darwincygwinmingw系统都启用了LTO支持。
从源码时使用-flto编译,所有的passesall_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中如果你想知道你的代码从前端开始的编译过程中做了哪些pass,可使用fdump-tree-all-fdump-ipa-all或者-fdump-rtl-all打印出来,如下:

gcc -flto -O3 .....  -fdump-tree-all test.c    // 可使用任意选项,此处打印flto 和O3

编译优化之 - 链接时优化(LTO)入门_第2张图片
  由上图可看出,源码经过前端处理之后在中端需要经过很多处理,你可以结合gdb看到每一个处理过程的转换信息。
对于最简单的 printf(“hello world”),在GCC中没有-flto选项的情况下得到的汇编码如下:
编译优化之 - 链接时优化(LTO)入门_第3张图片
使用-flto之后的大致输出信息如下:
编译优化之 - 链接时优化(LTO)入门_第4张图片
由此看出:LTO输出不包含目标代码,而仅包含LTO信息。
关于flto优化的具体信息可通过--verbose打印出来:
编译优化之 - 链接时优化(LTO)入门_第5张图片
在以上标注的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编不过。

References:

  • ThinLTO: Scalable and Incremental LTO
  • ThinLTO — Clang 10 documentation
  • GCC online documentation - GNU Project - Free Software Foundation (FSF)
  • Link Time Optimization (LTO),C++/D cross-language optimization

你可能感兴趣的:(编译器,linux)