ARM公司开发的编译工具从诞生一刻起,就成为业界针对ARM处理器最有效率的工具,因为它是总结了无数经验技巧,由ARM处理器的开发者ARM公司推出的开发工具。而许多前人的经验技巧就隐藏在编译器的某个角落里,等待着你去发现并加以利用。
本文就将与大家分享一些鲜为人知,但却可能使你大吃一惊的ARM编译、链接工具使用方法。
1.系统高效的关键字
自从可编程的处理器出现之后,软件程序员就没有停止过对代码优化、代码高效的追求。在当今世界,电子产品领域每天都在更新,竞争异常激烈,单纯地追求系统能够正常运行、得到正确的结果,是远远不够的。现实要求系统开发人员在产品中实现最优化的代码。那么“高效”的具体含义是什么呢?站在开发者的角度,可以简单地归纳为以下几点:代码执行速度、代码密度、系统吞吐量和系统的功耗。毫无疑问,这4点正是衡量一个产品是否成功所需的关键字,而且这四者之间还有很多相互影响或者相互促进的联系。
RVDS是ARM公司继SDT与ADS1.2之后主推的新一代开发工具,目前最高版本是3.0。它由RealView编译器(RVCT)、RealView汇编器(armasm)、RealView链接器(armlinker),以及RealView调试器(RVDebugger)组成。值得一提的是,在以上所提到的关键字中,有很多(如代码密度的提升、代码执行速度的提高)都可以由ARM开发工具RVDS自动实现,而不需要软件开发人员花费过多的时间手动优化高级语言代码。这也正是RVDS的优势所在。
由于很多嵌入式软件工程师都对ARM的开发工具RVDS非常熟悉,很多OEM厂商都在使用ARM
RVDS作为主要开发工具进行产品开发。所以本文将重点介绍ARM
RVDS工具的简单内部工作机理,这部分内容常常会被忽略,而掌握了这些内容很可能会帮助我们编写出更高效的代码。
2.使用RVDS编译器实现高效代码
RVCT是RVDS的编译工具,它可以提供多种优化级别,帮助开发人员完成代码密度与代码执行速度上的不同层次优化。此外,RVCT的很多编译特性还可以帮助开发人员进一步提升代码效率。
2.1 RVCT的优化级别与优化方向
提到RVCT就不能不提armcc的4个优化级别和2个编译选项,分别是-O1、-O2、-O3、-O4,以及-Otime、-Ospace。-Ospace和-Otime负责提供代码优化的大方向,告知编译器编译任务的主要目标是代码密度(-Ospace)还是代码性能(-Otime);而-O1、-O2、-O3、-O4则分别代表4种逐次递进的不同优化级别。
(1)Ospace还是Otime?
显然代码密度与代码执行速度在很多情况下是一对矛盾体。以下面的代码为例。例1中左右两段代码可以完成相同的任务,但是左边的有较高的代码密度,右边的则有较高的执行速度。因为当expr = 0且标志循环结束时,右边的代码可以顺序执行下去;而左边代码必须先跳转至循环体首部判断expr的值,随后再跳转至循环体尾,继续执行下一条指令。
例1 代码执行速度与代码密度的对比。
while (expr) {if (expr) do {
do
body;{ body; }
while (expr);
} }
那么什么时候使用Otime,什么时候使用Ospace呢?这需要开发人员根据系统实际需求来决定,最好是在两者之间找到一个合适的平衡点,而不是单纯地追求高速度或者小尺寸。也就是针对不同的代码模块根据其特性分别使用不同的编译选项。
此外,RVCT编译器支持很多非常有用的编译选项,如--no_inline(取消所有代码的内联函数)、--split_ldm(限制LDM/STM指令的最大操作寄存器数目)、--split_sections(将每个函数,而不是源文件,作为一个编译单元进行操作)等。
编译器的所有这一切都可以严格满足开发者的要求,帮助开发人员得到系统真正需要的优化过的代码。
(2)O3还是O2?
老的开发工具(如ADS1.2)中,只有3种递进的代码优化级别。对应3种编译选项,即-O0(Minimum optimization)、-O1(Restricted optimization)和-O2(High optimization )。使用-O0编译选项时,RVCT编译器只对代码进行最基本的优化操作,编译结束后用户得到的代码与用户手写源代码之间的差距很小。这种特性的主要作用是方便用户在程序开发阶段的调试工作,避免由于优化而产生的调试屏障。此外,很多资深软件工程师偏向于手写优化代码,在这种情况下,由于代码已经被优化过,可以使用-O0编译选项以减小RVCT的工作量,节省编译链接的时间。
-O1与-O2则是相对于-O0更加高级别的编译优化选项。前者提供有限的优化,后者可对代码进行较大程度的优化改进操作。RVDS中新增加了-O3(Maximum optimization)编译选项,它可以最大程度地发挥RVCT编译器的优势,将代码编译成最优。-O3与-O2都是较高级别的编译优化选项,但-O3的主要优势有以下几点。当用户使用 O3选项时:
编译器会自动对代码进行髙阶标量优化,即编译器根据代码特点,针对循环、指针等进行髙阶优化;编译器会把尽可能多的函数编译为内联(inline)函数;multifile compilation功能被自动使能。
(3)对于循环与指针的髙阶优化
当编译选项为-O3
-Otime时,RVCT会根据代码的具体情况,针对循环、指针等部分进行髙阶优化,如循环解开、融合、位置调整、指针优化等。以例2的函数为例。
例2 一段简单的C循环函数,在循环中含有数组指针调用。
CodeA
void increment(int *restrict b, int *restrict c) {
int i;
for (i=0; i<100; i++) {
c[i]=b[i] + 1;
}
}
CodeB
void increment(int *b, int *c) {
int i;
int *pb, *pc;
int b3, b4;
pb=b-1; pc=c-1;
b3=pb[1];
for (i = (100 / 2); i != 0; i--) {
b4 = *(pb += 2);
pc[1] = b3 + 1;
b3 = pb[1];
*(pc += 2) = b4 + 1;
}
}
仔细观察可以发现,CodeA与CodeB可以实现同样的功能,即将数组b的每个成员加1赋值给数组c的对应成员。但是与CodeA相比,CodeB具有更高的执行速度。主要体现在:
① 循环100次变成了循环50次,减少了跳转次数;
② 数组变成了指针,减少了每次计算数组偏移量的指令;
③ 微调了不同代码操作的执行顺序,减少了流水线stall的情况
④ 循环从++循环变成了--循环,可以使用ARM指令的条件位,为每次循环减少了一条判断指令。
很多程序员就是通过手写不同的C代码,提高了代码执行效率。在RVDS中,使用-O3 -Otime编译选项,RVCT会自动帮助程序员进行这些髙阶标量优化,即直接将CodeA优化成以前由CodeB才能得到的汇编代码。虽然优化之后函数的代码尺寸大于原先的函数,但是执行速度也大大提高。经过统计,使用EEMBC benchmarking,-O3编译选项编译得到的最终代码平均性能相对于 O1可以有10%的提升,而总体代码尺寸只增加了1%。
2.2 multifile compilation
按照传统的编译方式,先把各个C或C++文件单独编译成.obj文件,再将这些目标文件链接在一起。虽然在编译单独的C或C++文件时,编译器会充分发挥其优化特性;但此时编译器无法关注到大量的C或C++文件接口之间可以优化的部分。所以在传统的编译结果里,还有许多优化的余地。如何才能让编译器同时关注和编译所有的源代码呢?
multifile compilation是RVDS一个较新的特性,它可以帮助开发人员将所有的源文件作为一个compilation unit进行编译,并最终生成一个大的目标文件。mutifile compilation给软件开发人员带来的直接优势有以下几点:
① 增大了inline的可能性。由于inline只能发生在一个compilation unit中,所以在没有使用mutifile compilation时,inline只能发生在一个源文件范围内。multifile compilation将一个compilation unit扩大到了所有源文件的范围上,所以直接增加了inline发生的几率。
② 增大了基地址与函数间优化的可能性。同inline一样,所有的基地址与函数间的优化也必须在一个compilation unit中,随着conpilation unit的扩大这种优化的可能性也增加了。
③ 降低了scatter file的复杂性