Keil Optimization Level 选择

最新版本Keil MDK 默认不带有AC5编译器了(可以自己安装AC5),Keil编译器提供了丰富的优化选项,用于平衡代码大小和性能之间的关系。相比AC5,使用AC6会增加几个优化选项:代码大小、速度、平衡等。

../_images/Optimization_Level.png

优化选项包含:-O0 -O1 -O2 -O3 -Ofast -Os -Oz -Omax 选项,重点需求:

Optimization goal

Useful optimization levels

Smaller code size

-Os, -O2

Faster performance

-O2, -O3, -Ofast, -Omax

Good debug experience without code bloat

-O1

Better correlation between source code and generated code

-O0

Faster compile and build time

-O0

Balanced code size reduction and fast performance

-Os

优化级别 -O0

  • 特点:

    • 禁用所有优化,编译器不会对代码做任何优化处理,生成的目标代码和源代码的结构、顺序高度一致。

    • 编译器处理流程最简单,编译和链接速度最快。相比其他优化等级(如 -O2、-O3),-O0 的编译时间最短。

    • 与其他优化级别相比,代码大小和堆栈使用率明显更高。

    • 生成的代码与源代码紧密相关,但是生成的代码量更大,包括无用的代码。

    • 与其他优化级别相比,调试体验可能会更好。

    • 与其他优化级别相比,代码质量可能会更好。

    • 与其他优化级别相比,代码大小和性能之间的关系可能会更好。

优化级别 -O1

  • 特点:使用此选项以获得良好的调试体验。

    • 在编译器中启用核心优化。

    • 代码质量好。

    • 堆栈使用率提高。

    • -O1 做适度优化,调试体验比 -O2、-O3 好,且不会像 -O0 那样完全无优化。

    • 性能可能会更好。

    • 代码大小和性能之间的关系可能会更好。

  • -O1 与 -O0 相比,使用时的区别是:

    • 启用优化,这可能会降低调试信息的完整度。

    • 启用了内联和尾调用,这意味着回溯可能无法提供打开功能激活的堆栈。

    • 不会调用没有使用,或没有预期调用的函数,代码量更小。

    • 变量的值在不使用后可能在其范围内不可用。例如,它们的堆栈位置可能已被重用。

优化级别 -O2

  • -O2 会进行较多优化,包括去除冗余代码、循环优化等,通常能减小代码体积,并更改了优化的启发式方法。这是编译器可能生成矢量指令的第一个优化级别,还会降低调试体验。

  • -O2 与 -O1 相比使用时的差异是:

    • 编译器认为内联调用站点可获利的阈值可能会增加。

    • 执行的循环展开数量可能会增加。

    • 可以为简单循环和独立标量运算的相关序列生成矢量指令【矢量指令(Vectorization) 是编译器自动将标量操作转化为SIMD(单指令多数据)指令的过程,可以提升数据并行处理的效率】。

  • 可以使用 armclang 命令行选项禁止创建矢量指令 -fno-vectorize。-fno-vectorize 用于编译器自动进行矢量化优化,默认情况下,较高优化等级(如 -O2、-O3)会自动启用矢量化。

    某些情况下,自动矢量化可能导致代码行为异常或调试困难,此时可以用 -fno-vectorize 禁止,生成的代码不会包含自动矢量化的 SIMD 指令。

优化级别 -O3

  • 特点:启用更多、更激进的优化选项,目的是最大化程序的执行速度,追求极致性能,适合对速度要求极高的场合,选择时需根据实际需求权衡。

    • 有更高的性能优化。

    • 允许进行需要大量编译时分析和资源的优化(对性能要求极高的核心算法、数值计算、信号处理等)。

    • 与 -O2 相比更改了优化的启发式方法。

    • -O3 指示编译器针对生成的代码的性能进行优化,由于循环展开、内联等激进优化,而忽略生成的代码的大小。

    • 代码大小增加,可能不适合资源受限的嵌入式系统。

  • -O3 与 -O2 相比使用时的差异是:

    • 编译器认为内联调用站点是有利可图的阈值增加。

    • 执行的循环展开量增加。

    • 在编译器管道中启用更积极的指令优化。

优化级别 -Os

  • 特点:-Os 目的是在不显著增加代码大小的情况下提供高性能。根据你的应用程序,提供的性能可能类似于 -O2 或 -O3。

  • -Os 与 -O3 相比,可减少代码大小。但会降低调试体验。

  • -Os 与 -O3 相比使用时的差异是:

    • 降低编译器认为内联调用站点可获利的阈值。

    • 显著降低了执行的循环展开量。

优化级别 -Oz

  • 特点:-Oz 目的是提供尽可能小的代码量。Arm 建议使用此选项以获得最佳代码大小,此优化级别会降低调试体验。

  • -Oz 与 -Os 相比使用时的差异是:

    • 编译器仅针对代码大小进行优化,而忽略性能优化,这可能会导致代码变慢。

    • 未禁用功能内联。在某些情况下,内联可能会整体上减少代码大小,例如,如果一个函数仅被调用一次。仅当预期代码大小会减小时,才将内联启发式方法调整为内联式。

    • 禁用可能会增加代码大小的优化,例如循环展开和循环矢量化。

    • 循环是作为 while 循环而不是 do-while 循环生成的。

优化级别 -Ofast

  • 特点:

    • 包含了 -O3 的所有优化内容,如循环展开、矢量化、函数内联、常量传播等。

    • -Ofast 通常会自动启用 -ffast-math,这会让编译器对浮点运算做更激进的优化,如合并表达式、忽略浮点运算的精度和舍入方式等。

    • -Ofast 会启用一些默认情况下不会开启的优化,这些优化可能会让程序的行为不完全符合 C/C++ 标准,但能进一步提升性能。

    • 与 -O3 相比,该级别会降低调试体验,并可能导致代码大小增加。

优化级别 -Omax

  • -Omax 是最大程度的优化,并专门针对性能优化。它支持从级别进行的所有优化,以及链接时间优化(Link-time Optimization LTO)。

  • 在此优化级别上,ARM Compiler 可能会违反严格遵守语言标准的规定。使用此优化级别可获得最快的性能。

  • 与 -Ofast 相比,该级别会降低调试体验,并可能导致代码大小增加。

  • 使用 -Omax 进行编译,并具有单独的编译和链接步骤,必须在 armlink 命令行中包括 -Omax。

  • 编译时用 -Omax,但链接时没加,链接器可能不会应用最高级别的优化,导致最终生成的程序没有达到期望的优化效果,甚至可能出现兼容性或性能问题。

    备注

    在coremark测试中需要注意对应配置,避免测试数据未达预期;

优化项选择对比

优化级别

主要目标

优化强度

代码体积

调试友好性

适用场景

-O0

无优化

最低

最大(无任何优化)

最好

调试、开发初期

-O1

基本优化

较大(略小于-O0)

较好

需调试且有一定优化需求

-O2

高级优化

中等(通常小于-O1)

一般

正式发布、性能与体积平衡

-O3

最高级别优化

最高

较大(可能大于-O2/-Os)

最差

性能极致要求(如算法核心)

-Ofast

激进优化(不保证标准)

极高

较大(与-O3类似或更大)

最差

极致性能,允许不完全遵循标准的场合

-Omax

编译器支持的最大优化

极高

较大(与-Ofast类似)

最差

追求极限性能,通常与-Ofast类似

-Os

优化代码体积

小(小于-O2,接近最小)

一般

存储空间有限的嵌入式系统

-Oz

极致优化代码体积

最小(所有等级中最小)

一般

追求最小体积,部分编译器(如clang)支持