深入浅出编译优化选项(上)

在前文《如何为嵌入式软件开发选择编译器》中讲到编译器对于嵌入式软件开发的重要性,以及如何选择一款优秀的编译器。文中也比较了现有主流编译器的编译优化性能,IAR Embedded Workbench编译器不论在输出代码体积还是性能均处于业界领先地位。

本文中,我们将以IAR Embedded Workbench编译器为例,阐述如何配置编译优化选项,以达到嵌入式软件代码性能和体积的最佳平衡。由于篇幅关系,本主题将会分成上、下两篇。

编译器代码构建过程

在介绍如何配置编译器优化选项之前,让我们先来了解下编译器构建的过程。以IAR Embedded Workbench编译器为例,其代码构建过程如下图。

构建过程分为两部分:前端和后端。

前端主要将C代码通过解析器(Parser)生产中间代码(Intermediate Code),在这个过程中会介入优化器(High-Level Optimizer),其优化策略包括函数内联(Function in lining), 冗余代码消除(Dead code elimination), 循环展开(Loop unrolling)等。

后端则将中间代码通过目标代码生成器(Code Generator)生成目标汇编代码(Target Code),在这个过程中也会介入优化器(Low-Level Optimizer),其优化策略包括调度(Scheduling), 窥视孔(Peephole), 函数调用优化(Cross call)等。然后由汇编器(Assembler)将汇编代码转换成目标机器码(Object Code)。最后,通过链接器(Linker),将所有的目标机器码链接成elf格式的可执行二进制代码文件。在链接阶段也有相应的优化器(Linker time optimization)进行优化。

IAR Embedded Workbench 编译优化选项介绍

在IAR Embedded Workbench(基于EWARM v9.32.2)中通过菜单栏(Project -> Options)打开项目选项界面,选中“C/C++ Compiler”栏目,并且在右边选项卡选中“Optimizations”,即可进行编译优化选项配置,如下图所示。

1. 编译优化等级配置

如下图所示,IAR Embedded Workbench共分为4个优化等级(None, Low, Medium, High),其中等级“High”又分为3个子优化等级(Balanced, Size, Speed)。下表是各个优化等级对应的优化策略总览。

  • 选择“None”,编译器只会进行无用代码消除等基本优化,但编译速度快,并且最适合C/C++源代码调试。
  • 最高优化等级除了High(Balanced)之外,也可以选择High(Size)对于代码体积进行极致优化;或者选择High(Speed) ,并且勾选 No size constraints,则对于代码性能进行极致优化。

关于编译优化微调项(Enabled transformations)配置方法,我们将在《深入浅出编译优化选项(下)》详细介绍。

2. 多文件编译(Multi-file Compilation)

通常情况下,编译器逐个编译C/C++源文件。编译器的单次编译视野仅限于当前C/C++源文件,不能获知其他源文件的情况,包括全局变量,函数调用,指针别名等关系。因此编译器会采用较为保守和安全的优化假设。

IAR Embedded Workbench则为用户提供了 “多文件编译” 功能,如下图。编译器在工程全局级别或者文件组级别,允许一次编译多个文件,从而增加了编译视野,可以进行更为深度的编译优化策略实施,通常能获得更好的编译优化效果。

3. 灵活配置编译优化选项作用域

IAR Embedded Workbench提供了不同作用域的编译优化选项设置,使用户能够更加灵活的进行配置,对不同编译优化需求的代码进行分别配置。

  • 全局设置:如之前介绍的编译优化选项设置方法均为全局设置,作用于整个工程范围。
  • 每个文件/每个组的设置:当文件/组中的所有函数必须具有与项目不同的编译优化设置时,打开文件/组编译选项界面(在项目管理器选中文件/组,右击鼠标选择Options),选择“Override inherited settings”,如下图。然后进行编译优化设置。

china-learn-programming-complier-9.png

  • 单个函数:当某些函数必须具有不同的编译优化设置时,可以在源代码中使用 #pragma 关键字进行单独配置,如下例。

4. 链接阶段优化选项

IAR Embedded Workbench在链接阶段也提供了相应的代码优化选项,如下图。

  • 链接阶段默认消除未使用的变量和函数。
  • 小函数内联(inline small routines):将对小函数的某些调用替换为函数的本体。
  • 合并重复段(Merge duplicate sections):链接器可以检测具有相同内容的只读段,并仅保留每个此类段的一个副本,从而将对任何重复段的所有引用重定向到保留的段。
  • C++虚拟函数消除(Perform C++ virtual function elimination):虚函数是 C++ 中的一种特殊函数,它的实际调用取决于对象的实际类型。虚函数的调用需要运行时动态解析,因此会导致一定的运行时开销。“C++虚拟函数消除” 可删除不需要的虚拟函数和动态运行时类型信息,以减少程序运行时的开销,提高程序的性能。

嵌入式软件编译优化选项配置整体思路

嵌入式系统中通常硬件资源有限,特别是存储器容量,直接与MCU硬件成本挂钩,影响整个系统的硬件物料成本;另一方面,嵌入式软件又需要对外部信号进行实时响应,对外部设备进行实时控制,比如马达控制组件;除此之外,电池供电的嵌入式设备需要具备低功耗特性,要求CPU尽可能处于深度睡眠状态,以尽可能延长设备的单次电池工作时间。结合上述情况,对于嵌入式软件进行编译优化选项配置需要进行差异化考量,具体如下:

  • 对于整个嵌入式软件项目作用域,可以考虑极致代码体积优化。较少的代码体积可以适配较小flash容量的MCU,从而节省硬件物料成本。
  • 对于运行频度很高的代码段,可以考虑极致代码性能优化。这样可以使得关键代码在相同CPU速度的情况下,达到更高的实时响应速度。
  • 另外,在电池供电的低功耗应用中,CPU一般处在深度睡眠状态。通常是间隔一段时间或者外部事件到来,唤醒CPU执行一个任务,然后CPU重新回到深度睡眠状态。设备功耗等于CPU深度睡眠的静态功耗和任务运行的动态功耗之和。根据MCU硬件电气特性,其单位时间动态功耗(mA级别)要远大于单位时间静态功耗(uA级别)。

对于经常需要唤醒执行的软件任务,可以考虑极致代码性能优化。这样会减少动态功耗的时间,以达到整体功耗的下降,从而增加设备的电池使用寿命。

注意事项:

  • #pragma optimize 只能降低对应函数的优化等级,或者选择另外一种优化策略。如果 pragma optimize的优化等级比编译器选项的优化等级高,那么 pragma optimize命令会被忽略。
  • 高优化等级会让编译时间变长,同时让调试变得困难一些(因为高优化等级会让生成的代码和源代码的对应关系没有那么直接)。如果在调试过程中发现类似的问题,建议降低优化等级进行调试。

总结

IAR Embedded Workbench是一款业界领先的编译工具链,除了提供卓越的性能之外,还提供了丰富灵活的编译优化选项配置,以帮助用户在不同的嵌入式应用需求下,都能找到最佳的嵌入式软件代码性能和体积的平衡点。

在下一篇《深入浅出编译优化选项(下)》中,我们将介绍编译过程中的优化策略以及如何在IAR Embedded Workbench配置编译优化微调项(Enabled transformations),让用户可以根据代码需求配置出更加精准的编译优化策略组合。

参考文献:

1. www.iar.com/e-book/e-book-cn

2. https://www.embedded.com/understanding-mcu-sleep-modes-and-energy-savings/

We do no longer support Internet Explorer. To get the best experience of iar.com, we recommend upgrading to a modern browser such as Chrome or Edge.