编译器调优手段

编译器优化选项能够在编译阶段完成代码的优化,下面列举了常用的优化编译选项及其优化原理,在一些场景下能够大幅提高程序的运行性能。

指令集和流水线

C/C++代码在编译时,编译器将源码翻译成CPU可识别的指令序列,写入可执行程序的二进制文件中。CPU在执行指令时,通常采用流水线的方式并行执行指令,以提高性能,因此指令执行顺序的编排将对流水线执行效率有很大影响。通常在指令流水线中要考虑:执行指令计算的硬件资源数量、不同指令的执行周期、指令间的数据依赖等等因素。我们可以通过通知编译器,程序所运行的目标平台(CPU)指令集、流水线,来获取更好的指令序列编排。

在GCC 9.1.0版本,支持了鲲鹏处理器所兼容的ARM v8指令集、流水线。

使用方法:

在GCC for openEuler编译器、毕昇编译器、GCC编译器高于9.1.0版本上,并在CFLAGS、CPPFLAGS里增加编译选项:

-mtune=tsv110 -march=armv8-a

优化等级

编译器使用-O优化等级来控制程序的优化程度,具体如表1所示。

表1 优化等级说明

优化等级

说明

-O

缺省为-O0,不添加任何优化选项,有着最快的编译速度和程序更有利于调试。

-O1

添加常用的优化选项。

-O2

相比于-O1,添加更多优化选项。

-O3

最高级别的优化层次,编译时间较长,生成执行速度更快的程序。

-Ofast

添加跟-O3一样的优化选项,还增加一些非标准的优化选项。

-Os

添加跟-O2一样的优化选项,还增加一些选项去减小程序代码。

-Og

增加调试信息。

GCC使用gcc -Q --help=optimizers -O2可以查看各个优化等级选择的优化选项。

编译器有着众多的优化选项,下面列举一些例子,编译器的能够优化的场景。

PGO

PGO(Profile-Guided Optimizations)是通过收集程序运行时的信息(Profile)进行优化决策。PGO需要两次编译运行,第一遍编译过程中,编译器会在程序中插入一些获取程序运行特征的函数或者指令,然后使用第一遍编译的程序运行,程序运行期间会将特征信息保存在文件中。在第二次编译过程中,首先读入第一次程序运行保存的程序特征文件,编译器根据这些运行特点指导各种编译优化技术进行优化决策,生成目标程序,然后用于性能测试。

PGO支持两种基于反馈信息的优化技术。一种方式采用编译器插桩、运行、反馈编译的流程;另外一种方式和系统的perf工具一起使用,不需要编译器插桩,通过perf工具运行程序、收集信息、反馈编译。下面介绍下编译器插桩的PGO使用方法,使用perf分析工具的PGO方法可以参考官网链接:

GCC使用方法

  1. 添加-fprofile-generate编译选项,打开插桩应用,生成profile信息的开关。

    gcc -O2 -fprofile-generate vec.cpp

  2. 运行程序,生成profile信息,即gcda文件。

    ./a.out

  3. 添加-fprofile-use编译选项,使用profile信息重新编译程序。

    gcc -O2 -fprofile-use vec.cpp

Clang使用方法

  1. 添加-fprofile-instr-generate编译选项,打开插桩应用,生成profile信息的开关。

    clang -O2 -fprofile-instr-generate vec.cpp

  2. 运行程序,生成profile信息,文件名默认为default.profraw。

    ./a.out

  3. 使用llvm-profdata工具将default.profraw转换成Clang能够识别的profile文件格式。

    llvm-profdata merge -output=code.profdata default.profraw

  4. 添加fprofile-instr-use编译选项指定profile信息重新编译程序。

    clang -O2 -fprofile-instr-use=code.profdata vec.cpp

PGO具体优化内容:

LTO

LTO(Link Time Optimization)是链接期间的程序优化,将多个中间文件合并在一起,形成一个全局调用图,从而进行全程序的优化,链接时优化是对整个程序的分析和跨模块的优化。

添加-flto编译选项即可打开LTO优化。因为LTO是在编译后的优化,因此可以解决多个.o文件互不感知的优化问题,可以在全局上对整个程序进行优化,优化内容参考优化等级。例如:全局的函数内联优化,比单个.o文件的内联优化更加全面;无用代码消除,由于跨文件原因,无法判断代码是否有被调用,而LTO则可以确定是否存在无用代码,减小代码体积。

需要注意的是,LTO在改善程序性能的同时也带来了编译时间过长,编译时内存占用变高的问题。为了减少开启LTO带来编译时间太长的问题,LLVM提出了ThinLTO技术,可以大幅降低编译时间,在LLVM编译器下增加-flto=thin即使用的是ThinLTO优化。

AutoTuner

AutoTuner一种自动化的迭代过程,通过操作编译选项来优化给定程序,以实现最佳性能。它由两个组件配合完成,毕昇编译器AutoTuner命令行工具。

AutoTuner调优流程分两个阶段:初始化编译阶段和调优阶段。

在初始化阶段,毕昇编译器增加-fautotune-generate编译选项进行一次编译,在编译的过程中,毕昇编译器会生成一些包含所有可调优结构的Yaml文件,告诉我们在这个目标程序中哪些结构可以用来调优。

在调优阶段,AutoTuner首先读取生成好的可调优结构的Yaml文件,从而产生对应的搜索空间,然后根据设定的搜索算法尝试一组参数的值,生成一个Yaml格式的编译配置文件,编译出目标程序二进制,最后AutoTuner将编译好的文件以用户定义的方式运行并取得性能信息作为反馈。经过一定数量的迭代之后,AutoTuner将找出最终的最优配置,生成最优编译配置文件,以Yaml的形式储存。

AutoTuner目前有两种使用方式,并对应两种不同的命令行工具llvm-autotuneauto-tuner具体使用方法参考《AutoTuner特性指南(毕昇编译器)