RISCV GCC 2

3.3 GCC中已有的优化
GCC 为采用GPL和LGPL 协议的开源免费软件,来源于GNU工程。GCC编译器包含C库文件glibc、调试器 GDB、核心工具组件和 binutils 等。自1987年GCC1.0 版本发布以来,经过30多年的发展,在平台支持和基础优化等方面得到全面发展。GCC编译器是一个复杂的大型系统软件,由多个pass子过程和组件组成;各个pass之间并不是简单地按顺序运行,它们经过统一的调度以执行对应的优化pass,有些pass可能需要多次运行,以完成各种复杂的优化转换。计算机科学中一些经典的理论技术被应用于编译器的优化方法中,例如图着色算法、贪婪算法、线性扫描算法、晶格理论和图算法等。
高性能的编译器能大幅提升应用程序性能,提高系统资源利用率。经过无数编译器研究者和开发者的共同努力,编译器愈加高效稳定,编译优化选项也愈加完善。GCC 编译器中的优化 pass 涉及中端和后端表示,已达到两百多个,GCC中包含了从-O0到-O3以及-Og,-Os和-Ofast各种级别的优化,并提供了上百多个可独立控制的优化标志。每个优化级别都启用了优化标志的子集,并在启用调试信息生成对可执行代码没有任何影响。但是确定何时运行pass是一个复杂的过程,每一个pass都有一个门函数(gate function), 它根据优化级别和标志来决定是否运行该pass。
编译器的优化级别对程序性能有着显著的影响。优化级别决定了编译器在编译过程中执行的优化类型和程度。不同的优化级别会应用不同的优化策略,从而影响程序的执行速度、内存使用、代码大小和编译时间。以下是一些常见的优化级别及其对程序性能的影响:
-O0(无优化):编译器不进行任何优化,直接将源代码转换为机器代码。优点:编译速度快,生成的代码易于调试。缺点:程序运行速度慢,内存使用可能较高。
-O1(启用基本优化):应用一些基本的优化,如死代码消除、常量传播、循环不变代码外提等。优点:在不显著增加编译时间的情况下提高程序性能。缺点:优化程度有限,可能无法充分利用硬件资源。
-O2(启用更多优化):应用更多的优化技术,如指令调度、循环优化、内联展开等。优点:进一步提高程序性能,通常比-O1级别有更显著的提升。缺点:编译时间增加,生成的代码可能更难调试。
-O3(启用所有优化):启用编译器支持的所有优化技术,包括-O2中的优化以及更激进的优化策略,如指令级并行、更广泛的内联展开等。优点:在大多数情况下能生成最快的代码。缺点:编译时间显著增加,生成的代码可能非常难以调试。
-Os(优化代码大小):专注于减少生成的代码大小,同时保持合理的性能。优点:生成的可执行文件更小,适合嵌入式系统或资源受限的环境。缺点:可能牺牲一些性能来减少代码大小。
-Ofast(启用非标准优化):启用-O3中的所有优化,并允许使用非标准的优化技术,如改变浮点数学的精度以提高性能。优点:可能进一步提高性能。缺点:生成的代码可能不符合标准,可能引入精度问题。
-flto(链接时优化):允许在链接时进行跨编译单元的优化,可以进一步提高性能。优点:可以跨多个文件进行优化,提高代码的执行效率。缺点:增加链接时间,需要所有编译单元的中间表示。
优化级别对程序性能的影响是多方面的,包括但不限于:
执行速度:优化级别越高,编译器越倾向于生成执行速度更快的代码。
内存使用:优化可以帮助减少程序的内存占用,尤其是在使用-Os级别时。
代码大小:优化级别越高,生成的代码可能越复杂,但通过特定的优化(如-Os)可以减少代码大小。
编译时间:优化级别越高,编译时间通常越长,因为编译器需要执行更多的分析和转换。
调试难度:优化级别越高,生成的代码越难以调试,因为优化可能会改变代码的结构和行为。
因此,选择合适的优化级别需要根据具体的应用场景和需求来决定。在性能要求高的情况下,可以使用更高的优化级别;而在开发和调试阶段,可能更倾向于使用较低的优化级别以便于调试。一般情况下,使用-Ofast来测试性能,使用-Os来测试代码大小。
在gcc/opts.c文件中主要进行一些默认选项的分配管理以及完成后的处理等操作。其中的default_options_table数组会根据优化级别设置启用标志,如添加-O级别上的选项可按照其中已有的选项要求进行添加,并在相关函数中配置其flag。-O0不做任何优化,-O2包含-O1的所有优化,-O3包含-O2的所有优化,default_options_table数组内容如下:
static const struct default_options default_options_table[] =
{
/* -O1 optimizations. /
{ OPT_LEVELS_1_PLUS, OPT_fdefer_pop, NULL, 1 },
#if DELAY_SLOTS
{ OPT_LEVELS_1_PLUS, OPT_fdelayed_branch, NULL, 1 },
#endif
{ OPT_LEVELS_1_PLUS, OPT_fguess_branch_probability, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fcprop_registers, NULL, 1 },
………………………………
/
-O2 optimizations. /
{ OPT_LEVELS_2_PLUS, OPT_finline_small_functions, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_findirect_inlining, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fpartial_inlining, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fthread_jumps, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fcrossjumping, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_foptimize_sibling_calls, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fcse_follow_jumps, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fgcse, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fexpensive_optimizations, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_frerun_cse_after_loop, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fcaller_saves, NULL, 1 },
……………………………………
/
-O3 optimizations. /
{ OPT_LEVELS_3_PLUS, OPT_ftree_loop_distribute_patterns, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_ftree_loop_distribution, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_floop_interchange, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fpredictive_commoning, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fsplit_paths, NULL, 1 },
/
Inlining of functions reducing size is a good idea with -Os
regardless of them being declared inline. /
{ OPT_LEVELS_3_PLUS_AND_SIZE, OPT_finline_functions, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_finline_functions_called_once, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fsplit_loops, NULL, 1 },
……………………………………………
/
-Ofast adds optimizations to -O3. */
{ OPT_LEVELS_FAST, OPT_ffast_math, NULL, 1 },
{ OPT_LEVELS_NONE, 0, NULL, 0 }
};
但是某些标志默认情况下是通过其初始化程序启用的,例如 gcc/common.opt,根据其他条件,有些也被强制启用或禁用。也就是说一个pass的启用或关闭是由众多条件构成的,只有满足这些条件,这个pass才会被执行。
随着程序设计语言和体系结构的不断变化和程序复杂性的提高,编译器内部也在不断的进行改进。经过研究人员数十年的研发,为满足日益増长的用户需求,编译优化技术己经发展成为包括基于策略、证据、性能监测、数据流敏感重写、语言相关、内存及代码位置变换、循环变换、全局代码调整和控制流图变换等多种多样的优化技术。
编译优化技术按照编译器前、中、后端的基本结构分为AST级、GIMPLE级、RTL级优化。AST级优化主要对编译前端语法分析、词法分析和抽象语法树做一些表达式化简化和无用赋值删除的简化;GIMPLE级优化在编译器中间代码级做机器无关优化,包括循环优化、函数内联、转移优化、公共子表达式删除和软件流水等。RTL级优化在编译器后端做机器相关优化。程序设计语言的更新迭代速度远不如处理器型号类型的更新速度,并且随着目标机体系结构的日趋复杂,更多与机器相关的性能可优化细节暴露在编译器后端而非前端,后端优化对处理器性能提升的贡献度占比将越来越大。因此国内外有关编译优化技术的研究集中在编译器后端的RTL级优化,广泛应用的RTL级编译优化技术有指令选择、指令调度、寄存器分配、循环优化、窥孔优化和延迟分支调度等。嵌入式交叉开发下的RISC-V微控制器片上存储空间十分有限,编译生成的可执行文件越小,微控制器便能执行更多高级程序的功能;可执行文件体积是影响微控制器性能的关键指标,对编译器进行优化十分重要;常见的编译后端优化方法中,窥孔优化是缩小目标文件体积的重要方法。窥孔优化主要有冗余指令删除、指令替换、强度削弱、循环展开和利用特殊指令几种方式。有文献记载移位替换乘法的指令替换方法减少编译运行时间10%,有文献表明冗余指令删除方法删除重复使用的load/store指令,降低了编译运行的功耗约13%。有文献记载利用乘加指令的特殊指令方法提高了指令并行化程度。同时也存在一些情况增加了优化,某类案例性能得到了提升,另外一些案例性能下降的情况。
3.4 GCC目录结构
GCC的源码可以从git上获得,进入到源码目录后,可以看到其主要目录结构,本方案主要是对RISC-V的gcc进行研究,riscv-gcc的目录结构如下图所示。

图3.3 riscv-gcc目录结构图
该源代码目录中的主要内容包括:
(1)与GCC编译配置相关的config文件;
(2)lib
目录:各种各样的库文件,既包括一些通用的库文件,也包含一些与语言相关的库文件,例如libcpp中包含与C++语言相关的代码库文件,libada中包含与ADA语言相关的代码库文件。
(3)gcc目录中包含GCC的核心代码,包括了与各种编程语言相关的词法、语法等前端分析程序,与各种目标机器相关的机器描述文件,以及与前端语言无关且与机器无关的核心处理代码等。
gcc目录下的gcc/cp、gcc/fortran、gcc/java、gcc/objc、gcc/objcp 等子目录就是与各种编程语言相关的处理部分,这几个目录分别处理编程语言C++、Fortran、Java、ObjectC、ObjectC++等,C语言的处理则是GCC默认的处理前端语言,其部分处理代码在gcc/目录中。
gcc目录下还包含了很多.c和.h文件,以下是一些关键文件及其作用的介绍:
主编译器文件:gcc.c:包含编译器的主逻辑,处理命令行参数和初始化编译器环境。此程序是C编译器和其他编译器的用户接口。因为编译是一个复杂的过程,涉及运行多个程序并在它们之间传递临时文件,有选择地将用户切换到这些程序,并在最后删除临时文件。CC识别如何通过文件名中的后缀编译每个输入文件。一旦它知道要执行哪种编译,编译过程就由一个名为“spec”的字符串指定。
优化相关文件:opts.c:它包含了编译器的命令行选项的处理代码。这个文件的主要作用是定义和实现 GCC 编译器支持的各种命令行选项。这些选项允许用户控制编译器的行为,例如启用或禁用特定的优化、设置调试信息的生成、指定输出文件的名称等。它使得用户可以通过命令行灵活地控制编译过程。
代码生成文件:emit-rtl.c:rtx代码和insns的中低级生成。此文件包含用于创建rtl表达式并在insns的双向链接链中操纵它们的支持函数。insn的模式是由insn-emit.c中与机器相关的例程创建的,该例程是根据机器描述自动生成的。这些例程使用“gen_rtx_fmt_ee”和genrtl中的其他函数来创建模式的单个rtx。由rtl.def自动生成;依赖于机器的是它们制作的rtx类型以及它们使用的参数。
错误处理文件:diagnostic.c:处理编译过程中出现的错误和警告信息, 此文件实现了诊断消息模块的语言无关方面.
语法分析文件:parse.c:实现语法分析器,负责解析源代码并生成抽象语法树(AST)。
数据结构定义:gcc.h:定义编译器使用的基本数据结构和类型。
优化相关头文件:opts.h:声明优化函数和相关数据结构。
错误处理头文件:diagnostic.h:声明错误处理相关函数和宏。
代码生成头文件:emit-rtl.h:声明代码生成相关的函数和数据结构。
语法分析头文件:parse.h:声明语法分析器使用的函数和数据结构。
配置文件:如config.h,用于定义编译器的配置选项和宏。
平台相关文件:根据不同的目标架构,可能会有特定的实现文件。
这些文件共同构成了GCC编译器的核心,负责从源代码到目标代码的整个编译过程。通过这些文件的协作,编译器能够实现高效的代码生成和优化。
(4)进一步查看gcc/config 目录中所包含的子目录,从目录的名称可以看出,这些目录分别对应了不同的目标机器名称。目录中包含的内容就是针对不同目标机器的机器描述文件,包括md文件及相应的c文件和h文件等。例如riscv目录中包含了RISC-V处理器的机器描述文件riscv.md等,如下图3.4所示,这个文件夹下的文件会在第六章有详细介绍。

图3.4 riscv配置目录结构图
3.5 GCC的编译
在获得了GCC的源代码后,为了生成目标机器上的编译器程序,需要对源代码进行编译,编译的一般步骤包括:
(1)使用configure脚本完成编译配置,生成Makefile 文件;
(2)使用make 工具编译源代码。
(3)使用make工具安装生成的编译程序等。
使用的典型脚本为:
./configure +配置选项
make
make install
这里的配置一般有一个需要强调的点:
用来描述生成、运行及目标系统的系统类型,通常由–build、–host及–target三个选项指定,其中:
build=BUILD:指定生成(build)编译器程序的机器和操作系统平台信息。 编译gcc的平台
host=HOST:指定生成的编译器程序所运行的机器和操作系统平台信息,默认值与BUILD 相同。 gcc运行的平台,编译程序。
target=TARGET:指定生成的编译器程序生成代码所运行的机器和操作系统平台信息默认值与 HOST 相同。 程序运行的平台 riscv
关于这几个选项的指定,通常会有如下的几种组合方式:
(1)BUILD、HOST、TARGET三者相同,这一般表示在本机进行GCC源代码的编译生成的编译器程序GCC也运行在与本机相同的机器和操作系统平台下,并且生成的编译器编译出的目标程序也将运行在同样的系统环境中。
(2)HOST与TARGET相同,但HOST与BUILD不同(生成native gcc),这是一种典型的生成交叉编译工具的情况,即在BUILD主机环境上编译GCC源代码,生成的编译器程序将运行在HOST主机环境中,并且该编译器程序编译生成的目标文件也将运行在HOST,即TARGET环境中。
(3)HOST与BUILD相同,但HOST与TARGET不同(生成交叉编译gcc),这也是一种典型的生成交叉编译工具的情况,即在BUILD主机环境上编译GCC源代码,生成的编译器程序将运行在HOST主机环境中,但是该编译器程序编译生成的目标文件将运行在TARGET环境中。

(4)BUILD、HOST及TARGET各不相同(这个没用过),这是一种最复杂的情况,也属于一种生成交叉编译工具的情况、即在BUILD主机环境上编译 GCC源代码,生成的编译器程序将运行在HOST主机环境中,并且该编译器程序编译生成的目标文件将运行在另一种TARGET机器环境中。目前我们使用的(1)(2)(3)种情况比较多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值