Arm嵌入式编译器可以执行一些优化来减少代码量并提高应用程序的性能。不同的优化级别有不同的优化目标,不仅如此,针对某个目标进行优化会对其他目标产生影响。比如想减小生成的代码量,势必会影响到该代码的性能。所以优化级别总是这些不同目标(代码量,程序性能,debug信息)之间的权衡。
目录
Optimization level -O0
Optimization level -O1
Optimization level -O2
Optimization level -O3
Optimization level -Os
Optimization level -Oz
Optimization level -Omin
Optimization level -Ofast
Optimization level -Omax
Arm Compiler for Embedded提供了各种优化级别来控制不同的优化目标:
优化目标 | 可用的优化级别 |
---|---|
更小的代码量 | -Oz , -Omin |
更快的性能 | -O2 , -O3 , -Ofast , -Omax |
兼顾代码量和debug信息 | -O1 |
源代码和生成代码之间更好的相关性 | -O0 (no optimization) |
更快的编译和构建时间 | -O0 (no optimization) |
在代码量和性能之间的平衡 | -Os |
- 如果对性能使用更高的优化级别,那么它对其他目标的影响也会更大,例如调试体验降低、代码大小增加和编译和构建时间增加。
- 如果优化目标是减少代码大小,那么它会对其他目标产生影响,例如降低调试体验、降低性能和增加编译和构建时间。
所以用户可以根据自己的编译目标,选择适合自己的编译优化等级:
Optimization level -O0
-O0 将禁用所有优化。
这个优化级别是默认的。使用-O0的结果是更快的编译和构建时间,但产生的代码,其性能比其他优化级别低,代码大小和堆栈使用也会明显高于其他优化级别。由于没有任何优化,所以生成的代码与源代码密切相关,这导致生成的代码明显更多,包括死代码(dead code)。
Optimization level -O1
-O1在编译器中启用内核优化(core optimizations)。-O1便于用户调试,代码质量优于-O0。此外,堆栈的使用也比使用-O0时得到了改善。如果想调试程序,建议使用-O1来获取更多的调试信息。与使用-O0相比,使用-O1的不同之处在于:
- 启用了优化,这可能会降低调试信息的保真度(fidelity)。
- 启用了内联,这意味着函数调用栈的回溯,可能不会像阅读源代码那样,具有层级关系,内联会把函数体直接加载到函数调用的地方。
- 如果不需要函数返回的结果,则一个没有副作用的函数在预期的地方可能不会被调用,甚至会被忽略。
- 局部变量的值在不再使用后可能无法在其作用域中被获取使用。例如,它们所在栈位置可能已经被其他模块使用了。
Optimization level -O2
与-O1相比,-O2对性能进行了更高的优化。这一层是编译器自动生成矢量指令(vector instructions)的第一个优化层。它还降低了调试体验,并且可能导致代码量的增加。与使用- O1相比,使用-O2的不同之处在于:
- 增加内联函数调用的阈值。
- 执行的循环展开次数可能会增加。
- 矢量指令可以为简单循环和独立标量操作的相关序列生成。可以使用armclang命令行选项-fno-vectorize来禁止矢量指令的创建。
Optimization level -O3
与-O2相比,-O3是更高的性能优化。此优化级别需要大量编译时分析和资源的优化,-O3指示编译器优化生成代码的性能,而忽略生成代码的量,这可能导致代码量增加。与-O2相比,它还降低了调试体验。并且:
- 增加内联函数调用的阈值。
- 执行的循环展开次数可能会增加。
- 在编译器流水线上,实施更加激进的指令优化策略。
Optimization level -Os
-Os的目标是在不显著增加代码大小的情况下提供高性能。取决于用户的代码,-Os提供的性能可能与-O2或-O3相似。与-O3相比,-Os减少了代码量。与-O1相比,它还降低了调试体验。与使用-O3相比,使用-Os的不同之处在于:
- 降低了内联函数调用的阈值。
- 执行的循环展开的数量显著降低。
Optimization level -Oz
-Oz的目标是在不使用链接时间优化(LTO)的情况下提供更小的代码量。如果LTO不适合用户的应用程序,Arm建议可以使用此选项以获得最佳代码大小。与-O1相比,此优化级别降低了调试体验。与使用-Oz相比:
- 编译器只针对代码大小进行优化,而忽略性能优化,这可能导致代码变慢。
- 未禁用函数内联。在某些情况下,内联可以减少总体代码大小,例如,如果一个函数只被调用一次。
- 禁止一些可能增加代码量的优化,比如将循环展开,循环矢量化等。
- 针对M系列的AArch32以及其他的AArch64目标,将使能外联(outlining)功能。外联器(outliner )将搜索代码中相同sequence的代码,并将它们放在同一个函数当中,然后用调用同一个函数的方式来取代这些相同的代码段。外联可以减小代码大小,但是增加了代码执行时间。用户可以使用-moutline, -mno-outline选项来手动开启或者关闭该功能。
Optimization level -Omin
通过使用LTO功能的子集,-Omin旨在提供比-Oz更小的代码量。与使用-Oz相比,使用-Omin的不同之处在于:
- -Omin支持一组基本的LTO,旨在删除未使用的代码和数据,同时还尝试优化全局内存访问。
- -Omin支持消除虚函数(C++)。
如果希望在-Omin下进行编译,并使用单独的编译和链接步骤,那么还必须在armlink命令行中包含-Omin。
Optimization level -Ofast
-Ofast执行-O3级的优化,包括那些使用armclang选项-ffast-math执行的优化。该级别还执行其他可能违反严格遵守语言标准的激进优化。与-O3相比,此级别降低了调试体验,并且可能导致代码大小增加。
Optimization level -Omax
-Omax执行最大优化,并专门针对性能优化。它支持从-Ofast到LTO的所有优化。在这个优化级别上,Arm嵌入式编译器可能会违反严格遵守语言标准。使用此优化级别可以获得最快的性能。与-Ofast相比此级别降低了调试体验,并且可能导致代码大小增加。如果用户希望在-Omax下进行编译,并且有单独的编译和链接步骤,那么您还必须在armlink命令行中包含-Omax。
示例
int test()
{
int x=10, y=20;
int z;
z=x+y;
return 0;
}
在上述代码中,int x=10
和z=x+y ,两行代码为死代码(
dead code),如果使用-O0,则不进行任何优化,这两行将会被编译生成到源文件中:
armclang --target=arm-arm-none-eabi -march=armv7-a -O0 -S file.c
如果使用-O1,这两行将会被忽略:
armclang --target=arm-arm-none-eabi -march=armv7-a -O1 -S file.c