RealView 编译工具产品 | 用户手册 |
C/C++ 编译器 | RV 编译器和库(RV_CC.PDF) |
C/C++运行时库 | RV 编译器和库 (RV_CC.PDF) |
RogueWave C++ 标准模板库 | RV 编译器和库 (RV_CC.PDF) |
Macro Assembler(宏汇编器) | RV 汇编器 (RV_ASM.PDF) |
Linker/Locater(链接器/定位器) | RV 链接器/工具 (RV_Link.PDF) |
Library Manager (库管理) | RV 链接器/工具 (RV_Link.PDF) |
HEX File Creator (十六进制文件生成器) | RV 链接器/工具 (RV_Link.PDF) |
启动代码(startup code)
例子:
:
:
AREA Reset, CODE, READONLY
:
:
EXPORT Reset_Handler
Reset_Handler
; CPU Reset Handler (executed after CPU Interrupt)
链接器配置
散列载入描述文件例子
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_ROM1 0x01000000 0x00010000 { ; load region
ER_ROM1 0x01000000 { ; load address = execution address
*.o (RESET, +First)
* (+RO)
}
RW_IRAM1 0x00000020 0x00001FE0 { ; RW data
* (+RW +ZI)
}
}
中断服务程序
RealView编译器提供关键字__irq 以定义标准的(irq)和(fiq)中断函数。
例子:
__irq void IRQ_Handler (void) {
/* the interrupt code */
}
对没有向量中断处理或FIQ中断函数的微控制器,需要按要求改变CPU的启动代码。
例子:
下面的C代码实现了一个空的 FIQ_Handler:
__irq void FIQ_Handler (void) {
;
}
在启动代码中,要进行如下修改以调用FIQ_Handler:
Vectors LDR PC, Reset_Addr
:
LDR PC, FIQ_Addr ; Instruction at FIQ Vector location
:
PRESERVE8 ; tell linker: stack alignment is 8-byte
IMPORT FIQ_Handler ; use external FIQ_Handler
FIQ_Addr DCD FIQ_Handler ; FIQ Entry point
软件中断管理
使用RealView编译工具可以很容易地创建SWI函数。在文件夹../ARM/Examples/SWI下提供了一个SWI的例子。
C中的SWI函数的定义如下:
int __swi(0) add (int i1, int i2); // function prototype for calling from C
int __SWI_0 (int i1, int i2) { // function implementation
return (i1 + i2);
}
SWI.S 定义了一个软件中断管理。在此文件下面的表中包含了应用程序SWI函数的入口。
:
IMPORT __SWI_0
IMPORT __SWI_1
IMPORT __SWI_2
IMPORT __SWI_3
SWI_Table
DCD __SWI_0 ; SWI 0 Function Entry
DCD __SWI_1 ; SWI 1 Function Entry
DCD __SWI_2 ; SWI 2 Function Entry
DCD __SWI_3 ; SWI 3 Function Entry
:
定位变量到指定的位置
使用定义在头文件 absacc.h中的__at 宏,可以将变量以如下方式定位到绝对地址处:
C 例子:
#include <absacc.h>
const char MyText[] __at (0x1F00) = "TEXT AT ADDRESS 0x1F00";
int x __at (0x40003000); // variable at address 0x40003000
汇编例子:
在汇编文件中可以使用段名,由|.ARM.__AT_<addr>|组成来定义位置。下面的例子是将一个段定义到地址0xFFE0处:
AREA |.ARM.__AT_0xFFE0|, CODE, READONLY
仿真外部I/O设备
外部I/O设备是典型的映射存储器。用户可以用µVision调试器提供的存储器窗口仿真这样的I/O设备。因为C用户程序不包含这样存储区域的任何变量声明,所以要求用MAP命令映射这段存储区。
MAP 0x100000, 0x100FFF READ WRITE /* MAP memory for I/O area */
循环优化
| |||
| |||
Home > Coding Practices > Optimizing code > Optimizing loops |
4.1.4. 循环优化
在大多数程序中,循环是一种常见的结构。由于大多数执行时间都耗费在循环上,因此有必要对循环做优化。
循环中止
循环中止条件要小心使用,否则会产生错误。一般有:
· 经常写递减到0的循环,使用简单的条件。
· 经常使用一个无符号整型的计数器,然后测试是否等于0。
表 4.1 显示了两个例子实现了计算 n!
,两个例子都显示了循环终止。第一个实现使用递增循环计算 n!
,而第二个实现使用递减循环计算 n!
。
表 4.1. C 递增和递减循环的C代码
递增循环 | 递减循环 |
int fact1(int n) { int i, fact = 1; for (i = 1; i <= n; i++) fact *= i; return (fact); } | int fact2(int n) { unsigned int i, fact = 1; for (i = n; i != 0; i--) fact *= i; return (fact); } |
表 4.2 显示了以上两个例子经过编译器编译之后产生汇编代码,C 代码都使用编译器选项 -O2
-Otime
。
表 4.2. C 递增和递减循环的汇编代码
递增循环 | 递减循环 |
fact1 PROC MOV r2, r0 MOV r0, #1 CMP r2, #1 MOV r1, r0 BXLT lr |L1.20| MUL r0, r1, r0 ADD r1, r1, #1 CMP r1, r2 BLE |L1.20| BX lr ENDP | fact2 PROC MOVS r1, r0 MOV r0, #1 BXEQ lr |L1.12| MUL r0, r1, r0 SUBS r1, r1, #1 BNE |L1.12| BX lr ENDP |
比较 表 4.2 显示了在递增循环汇编代码中的 ADD
/CMP
指令对在递减循环汇编代码中被替换成了一个单指令 SUBS
。这是由于与0比较可以进行优化。
为了保存循环中的一条指令,变量 n
在循环中不需要保存,因此变量的用法在递减汇编代码也要保存。这使得容易分配寄存器。
在初始化循环计数器时一般将其设为需要重复的次数,然后递减到0。这也同样适合于while 和 do while 语句。
循环展开
对于一些比较小的循环可以被展开以获得更高的执行速度,但可能代码长度增加。当循环被展开后,循环计数器值的刷新次数较少且跳转也较少。如果循环次数较少,完全可以展开,循环的开消几乎没有。ARM 编译器可在选中 -O3
-Otime
时自动展开循环。否则,需要源代码里展开。
注意
手动展开循环可能隐藏了循环的特点使得编充程序不能识别循环而没有对其进行循环的其它优化。
展开循环的好处与坏处都在 表 4.3 中有显示。两个程序通过移位后测试最低位以此来计算位数。
第一种方法是用一个循环计算位数。第二种方法将循环展开,一次循环计算4次,因此 n
每次移4位,减少了循环次数。这样编译器可以更好的优化。
Table 4.3. 展开和非展开计算位数的C 代码
位计算循环 | 展开的位计算循环 |
int countbit1(unsigned int n) { int bits = 0; while (n != 0) { if (n & 1) bits++; n >>= 1; } return bits; } | int countbit2(unsigned int n) { int bits = 0; while (n != 0) { if (n & 1) bits++; if (n & 2) bits++; if (n & 4) bits++; if (n & 8) bits++; n >>= 4; } return bits; } |
表 4.4 显示了 表 4.3 中例子通过编译器产生的汇编代码,这些C代码在编译时要使用选项 -O2
。
Table 4.4. 展开和非展开计算位数的汇编代码
位计算循环 | 展开的位计算循环 |
countbit1 PROC MOV r1, #0 B |L1.20| |L1.8| TST r0, #1 ADDNE r1, r1, #1 LSR r0, r0, #1 |L1.20| CMP r0, #0 BNE |L1.8| MOV r0, r1 BX lr ENDP | countbit2 PROC MOV r1, r0 MOV r0, #0 B |L1.48| |L1.12| TST r1, #1 ADDNE r0, r0, #1 TST r1, #2 ADDNE r0, r0, #1 TST r1, #4 ADDNE r0, r0, #1 TST r1, #8 ADDNE r0, r0, #1 LSR r1, r1, #4 |L1.48| CMP r1, #0 BNE |L1.12| BX lr ENDP |
对于ARM7,在最左边的那种实现方法的汇编代码中检测一位需要6个 周期。但它的代码仅有9条指令。而右边的那种展开循环的方法,每次循环检测4位,平均每位只需3周期。它的代价是较大的代码--15条指令。
使用volatile
如果将一个变量定义为 volatile 则相当于告诉编译器该变量可能随时被改变,例如被操作系统或硬件所改变。因为带有限定符 volatile 的变量可以在任何时刻改变,该变量的物理地址可能被频繁地访问。这就意味着编译器不能对这些变量实现优化,例如,将变量缓存到寄存器避免访问内存。
当一个变量的值可能在应用程序不知道的情况下可能改变其值,为了避免优化带来的问题,需要将其定义为 volatile 类型。当有以下情况时需要定义为 volatile 类型的变量:
- 访问内存映射的外围设备
- 在不同的进程之间共用全局变量
- 在中断服务程序中访问全局变量。
减少目标文件和库中的调试信息
在目标文件和库中减少调试信息是非常有益的。减少调试信息可以:
· 减少目标文件和库的大小,也就是减少了需要保存它们的磁盘存储空间。
· 加快了链接速度。在汇编阶段,大多数时间用在读取调试阶段信息和消除重复部分。
· 减小最终镜象的代码。这有助于快速装载和调试器预处理调试信息。
以下有几种方法可用来减少每个源文件产生的调试信息:
· 避免在头文件中条件使用 #define
。调试器不能移除共用的调试部分,除非这些部分是一样的。
· 更改C/C++源文件以使所有头文件包含文件 #included
有相同的顺序。
· 将头文件信息分成几个小块。这就是说,尽量使用数量较多的小头文件而不使用较大的单一头文件。这有且于链接器估量更多的通用块。
· 当在C/C++中需要用到时才包含头文件。
· 避免重复包含头文件。例如,如果有一个头文件 foo.h
,增加:
· #ifndef foo_h
· #define foo_h
· ...
· // rest of header file as before
· ...
· #endif /* foo_h */
可以使用编译器选项 --remarks
以产生警告信息。
· 在编译时,选择命令行选项 --no_debug_macros
从调试表中丢弃预处理宏定义。
函数
为了保持编译器更好地执行优化,一个好的办法是保持函数短小精悍。有一些方法可达到这点。例如,可以:
· 尽量减小传递给函数和从函数传递过来的参数个数;
· 从一个函数通过寄存器返回多个值可使用__value_in_regs
;
· 如果可能,限定函数为 __pure
。
· 在一般情况下,对于纯函数只需要调用一次。因为在相同调用的情况下调用的结果是相同的,所有以后调用该函数可以被第一次调用所返回的值所取代。这就是编译器优化的一个例子(Common Subexpression Elimination (CSE) )。