资料介绍
14.7 寄存器分配
编译器一项很重要的优化功能就是对寄存器的分配。与分配在寄存器中的变量相比,分配到内存的变量访问要慢得多。所以如何将尽可能多的变量分配到寄存器,是编程时应该重点考虑的问题。
注意当使用-g或-dubug选项编译程序时,为了确保调试信息的完整性,寄存器分配的效率比不使用-g或-dubug选项低很多。
14.7.1 变量寄存器分配
一般情况下,编译器会对C函数中的每一个局部变量分配一个寄存器。如果多个局部变量不会交迭使用,那么编译器会对它们分配同一个寄存器。当局部变量多于可用的寄存器时,编译器会把多余的变量存储到堆栈。这些被写入堆栈需要访问存储器的变量被称为溢出(Spilled)变量。
为了提高程序的执行效率:
· 使溢出变量的数量最少;
· 确保最重要的和经常用到的变量被分配在寄存器中。
可以被分配到寄存器的变量包括:
· 程序中的局部变量;
· 调用子程序时传递的参数;
· 与地址无关变量。
另外,在一些特定条件下,结构体中的域也可以被分配到寄存器中。
表14.1显示了当C编译器采用ARM-Thumb过程调用标准时,内部寄存器的编号、名字和分配方法。
表14.1 C编译器寄存器用法
寄存器编号可选寄存器名特殊寄存器名寄存器用法
r0a1函数调用时的参数寄存器,用来存放前4个函数参数和存放返回值。在函数内如果将这些寄存器用作其他用途,将破坏其值。
r1a2
r2a3
r3a4
r4v1通用变量寄存器
r5v2
r6v3
r7v4
r8v5
r9v6或SB或TR平台寄存器,不同的平台对该寄存器的定义不同
r10v7通用变量寄存器。在使用堆栈边界检测的情况下,r10保存堆栈边界的地址
r11v8通用变量寄存器。
r12IP临时过渡寄存器,函数调用时会破坏其中的值
r13SP堆栈指针
r14LR链接寄存器
r15PC程序计数器
从表14.1可以看出,编译器可以分配14个变量到寄存器而不会发生溢出。但有些寄存器编译器会有特殊用途(如r12),所以在编写程序时应尽量限制变量的数目,使函数内部最多使用12个寄存器。
注意在C语言中,可以使用关键词register给指定变量分配专用寄存器。但不同的编译器对该关键词的处理可能不同,使用时要查阅相关手册。
14.7.2 指针别名
C语言中的指针变量可以给编程带来很大的方便。但使用指针变量时要特别小心,它很可能使程序的执行效率下降。在一个函数中,编译器通常不知道是否有2个或2个以上的指针指向同一个地址对象。所以编译器认为,对任何一个指针的写入都将会影响从任何其他指针的读出,但这样会明显降低代码执行的效率。这就是著名的“寄存器别名(Pointer Aliasing)”问题。
注意一些编译器提供了“忽略指针别名”选项,但这可能给程序带来潜在的bug。ARM编译器是遵循ANSI/ISO标准的编译器,不提供该选项。
1.局部变量指针别名问题
通常情况下,编译器会试图对C函数中的每一个局部变量分配一个寄存器。但当局部变量是指向内存地址的指针时,情况有所不同。先来看一个简单的例子。
void add(int * i)
{
int total1=0,total2=0;
total1+= *i;
total2+= *i;
}
编译后生成:
add:
0000807C E3A01000 MOV r1,#0
》》》 POINTALIAS\#3 int total1=0,total2=0;
00008080 E3A02000 MOV r2,#0
》》》 POINTALIAS\#5 total1+= *i;
00008084 E5903000 LDR r3,[r0,#0]
00008088 E0831001 ADD r1,r3,r1
》》》 POINTALIAS\#6 total2+= *i;
0000808C E5903000 LDR r3,[r0,#0]
00008090 E0832002 ADD r2,r3,r2
》》》 POINTALIAS\#8 }
00008094 E12FFF1E BX r14
》》》 POINTALIAS\#11 {
注意程序中i的值被装载了两次。因为编译器不能确定指针*i是否有别名存在,这就使得编译器不得不增加一条额外的Load指令。
另一个问题,当在函数中要获得局部变量地址时,这个变量就被一个指针所对应,就可能与其他指针产生别名。为了防止别名发生,在每次对变量操作时,编译器就会从堆栈中重新读入数据。考虑下面的例子程序,分析其产生的编译结果。
void f(int *a);
int g(int a);
int test1(int i)
{ f(&i);
/* now use ’i’ extensively */
i += g(i);
i += g(i);
return i;
}
编译结果如下所示。
test1
STMDB sp!,{a1,lr}
MOV a1,sp
BL f
LDR a1,[sp,#0]
BL g
LDR a2,[sp,#0]
ADD a1,a1,a2
STR a1,[sp,#0]
BL g
LDR a2,[sp,#0]
ADD a1,a1,a2
ADD sp,sp,#4
LDMIA sp!,{pc}
从上面代码的编译结果可以看出,对每一次i操作,编译器都将会从堆栈中读出其值。这是因为,一旦在函数中出现对i的取值操作,编译器就会担心别名问题。为了避免这种情况,尽量不要在程序中使用局部变量地址。如果必须这么做,那么可以在使用之前先把局部变量的值复制到另外一个局部变量中。下面的程序是对test1函数的优化。
int test2(int i)
{
int dummy = i;
f(&dummy);
i = dummy;
/* now use ’i’ extensively */
i += g(i);
i += g(i);
return i;
}
编译后的结果如下。
test2
STMDB sp!,{v1,lr}
STR a1,[sp,#-4]!
MOV a1,sp
BL f
LDR v1,[sp,#0]
MOV a1,v1
BL g
ADD v1,a1,v1
MOV a1,v1
BL g
ADD a1,a1,v1
ADD sp,sp,#4
LDMIA sp!,{v1,pc}
从编译结果可以看出,修改后的代码只使用了2次内存访问,而test1为4次内存访问。
总上所述,为了在程序中避免指针别名,应该做到:
· 避免使用局部变量地址;
· 如果程序中出现多次对同一指针的访问,应先将其值取出并保存到临时变量中。
2.全局变量
通常情况下,编译器不会为全局变量分配寄存器。这样在程序中使用全局变量,很可能带来内存访问上的开销。所有尽量避免在循环体内使用全局变量,以减少对内存的访问次数。
如果在一段程序体内大量使用了同一个全局变量,建议在使用前先将其拷贝到一个局部的临时变量中,当完成对它的全部操作后,再将其写回到内存。
比较下面两个完成同样功能的函数,分析全局变量的操作对程序性能的影响。
int f(void);
int g(void);
int errs;
void test1(void)
{
errs += f();
errs += g();
}
void test2(void)
{
int localerrs = errs;
localerrs += f();
localerrs += g();
errs = localerrs;
}
编译结果如下。
test1
STMDB sp!,{v1,lr}
BL f
LDR v1,[pc, #L00002c-。-8]
LDR a2,[v1,#0]
ADD a1,a1,a2
STR a1,[v1,#0]
BL g
LDR a2,[v1,#0]
ADD a1,a1,a2
STR a1,[v1,#0]
LDMIA sp!,{v1,pc}
L00002c
DCD |x$dataseg|
test2
STMDB sp!,{v1,v2,lr}
LDR v1,[pc, #L00002c-。-8]
LDR v2,[v1,#0]
BL f
ADD v2,a1,v2
BL g
ADD a1,a1,v2
STR a1,[v1,#0]
LDMIA sp!,{v1,v2,pc}
从编译的结果中可以看出,test1中每次对全局变量errs的访问都会使用耗时的Load/Store指令;而test2只使用了一次内存访问指令。这对提高程序的整体性能有很大帮助。
3.指针链
指针链(Pointer Chains)常被用来访问结构体内部变量。下面的例子显示了一个典型的指针链的使用。
typedef struct { int x, y, z; } Point3;
typedef struct { Point3 *pos, *direction; } Object;
void InitPos1(Object *p)
{
p-》pos-》x = 0;
p-》pos-》y = 0;
p-》pos-》z = 0;
}
上面的代码每次使用“p-》pos”时都会对变量重新取值。为了提高代码效率,将程序改写如下。
void InitPos2(Object *p)
{
Point3 *pos = p-》pos;
pos-》x = 0;
pos-》y = 0;
pos-》z = 0;
}
经过改写的代码,减少了内存访问次数,提高程序的执行效率,另外也可以在object结构体中增加一个point3域,专门作为指向p-》pos的指针。
编译器一项很重要的优化功能就是对寄存器的分配。与分配在寄存器中的变量相比,分配到内存的变量访问要慢得多。所以如何将尽可能多的变量分配到寄存器,是编程时应该重点考虑的问题。
注意当使用-g或-dubug选项编译程序时,为了确保调试信息的完整性,寄存器分配的效率比不使用-g或-dubug选项低很多。
14.7.1 变量寄存器分配
一般情况下,编译器会对C函数中的每一个局部变量分配一个寄存器。如果多个局部变量不会交迭使用,那么编译器会对它们分配同一个寄存器。当局部变量多于可用的寄存器时,编译器会把多余的变量存储到堆栈。这些被写入堆栈需要访问存储器的变量被称为溢出(Spilled)变量。
为了提高程序的执行效率:
· 使溢出变量的数量最少;
· 确保最重要的和经常用到的变量被分配在寄存器中。
可以被分配到寄存器的变量包括:
· 程序中的局部变量;
· 调用子程序时传递的参数;
· 与地址无关变量。
另外,在一些特定条件下,结构体中的域也可以被分配到寄存器中。
表14.1显示了当C编译器采用ARM-Thumb过程调用标准时,内部寄存器的编号、名字和分配方法。
表14.1 C编译器寄存器用法
寄存器编号可选寄存器名特殊寄存器名寄存器用法
r0a1函数调用时的参数寄存器,用来存放前4个函数参数和存放返回值。在函数内如果将这些寄存器用作其他用途,将破坏其值。
r1a2
r2a3
r3a4
r4v1通用变量寄存器
r5v2
r6v3
r7v4
r8v5
r9v6或SB或TR平台寄存器,不同的平台对该寄存器的定义不同
r10v7通用变量寄存器。在使用堆栈边界检测的情况下,r10保存堆栈边界的地址
r11v8通用变量寄存器。
r12IP临时过渡寄存器,函数调用时会破坏其中的值
r13SP堆栈指针
r14LR链接寄存器
r15PC程序计数器
从表14.1可以看出,编译器可以分配14个变量到寄存器而不会发生溢出。但有些寄存器编译器会有特殊用途(如r12),所以在编写程序时应尽量限制变量的数目,使函数内部最多使用12个寄存器。
注意在C语言中,可以使用关键词register给指定变量分配专用寄存器。但不同的编译器对该关键词的处理可能不同,使用时要查阅相关手册。
14.7.2 指针别名
C语言中的指针变量可以给编程带来很大的方便。但使用指针变量时要特别小心,它很可能使程序的执行效率下降。在一个函数中,编译器通常不知道是否有2个或2个以上的指针指向同一个地址对象。所以编译器认为,对任何一个指针的写入都将会影响从任何其他指针的读出,但这样会明显降低代码执行的效率。这就是著名的“寄存器别名(Pointer Aliasing)”问题。
注意一些编译器提供了“忽略指针别名”选项,但这可能给程序带来潜在的bug。ARM编译器是遵循ANSI/ISO标准的编译器,不提供该选项。
1.局部变量指针别名问题
通常情况下,编译器会试图对C函数中的每一个局部变量分配一个寄存器。但当局部变量是指向内存地址的指针时,情况有所不同。先来看一个简单的例子。
void add(int * i)
{
int total1=0,total2=0;
total1+= *i;
total2+= *i;
}
编译后生成:
add:
0000807C E3A01000 MOV r1,#0
》》》 POINTALIAS\#3 int total1=0,total2=0;
00008080 E3A02000 MOV r2,#0
》》》 POINTALIAS\#5 total1+= *i;
00008084 E5903000 LDR r3,[r0,#0]
00008088 E0831001 ADD r1,r3,r1
》》》 POINTALIAS\#6 total2+= *i;
0000808C E5903000 LDR r3,[r0,#0]
00008090 E0832002 ADD r2,r3,r2
》》》 POINTALIAS\#8 }
00008094 E12FFF1E BX r14
》》》 POINTALIAS\#11 {
注意程序中i的值被装载了两次。因为编译器不能确定指针*i是否有别名存在,这就使得编译器不得不增加一条额外的Load指令。
另一个问题,当在函数中要获得局部变量地址时,这个变量就被一个指针所对应,就可能与其他指针产生别名。为了防止别名发生,在每次对变量操作时,编译器就会从堆栈中重新读入数据。考虑下面的例子程序,分析其产生的编译结果。
void f(int *a);
int g(int a);
int test1(int i)
{ f(&i);
/* now use ’i’ extensively */
i += g(i);
i += g(i);
return i;
}
编译结果如下所示。
test1
STMDB sp!,{a1,lr}
MOV a1,sp
BL f
LDR a1,[sp,#0]
BL g
LDR a2,[sp,#0]
ADD a1,a1,a2
STR a1,[sp,#0]
BL g
LDR a2,[sp,#0]
ADD a1,a1,a2
ADD sp,sp,#4
LDMIA sp!,{pc}
从上面代码的编译结果可以看出,对每一次i操作,编译器都将会从堆栈中读出其值。这是因为,一旦在函数中出现对i的取值操作,编译器就会担心别名问题。为了避免这种情况,尽量不要在程序中使用局部变量地址。如果必须这么做,那么可以在使用之前先把局部变量的值复制到另外一个局部变量中。下面的程序是对test1函数的优化。
int test2(int i)
{
int dummy = i;
f(&dummy);
i = dummy;
/* now use ’i’ extensively */
i += g(i);
i += g(i);
return i;
}
编译后的结果如下。
test2
STMDB sp!,{v1,lr}
STR a1,[sp,#-4]!
MOV a1,sp
BL f
LDR v1,[sp,#0]
MOV a1,v1
BL g
ADD v1,a1,v1
MOV a1,v1
BL g
ADD a1,a1,v1
ADD sp,sp,#4
LDMIA sp!,{v1,pc}
从编译结果可以看出,修改后的代码只使用了2次内存访问,而test1为4次内存访问。
总上所述,为了在程序中避免指针别名,应该做到:
· 避免使用局部变量地址;
· 如果程序中出现多次对同一指针的访问,应先将其值取出并保存到临时变量中。
2.全局变量
通常情况下,编译器不会为全局变量分配寄存器。这样在程序中使用全局变量,很可能带来内存访问上的开销。所有尽量避免在循环体内使用全局变量,以减少对内存的访问次数。
如果在一段程序体内大量使用了同一个全局变量,建议在使用前先将其拷贝到一个局部的临时变量中,当完成对它的全部操作后,再将其写回到内存。
比较下面两个完成同样功能的函数,分析全局变量的操作对程序性能的影响。
int f(void);
int g(void);
int errs;
void test1(void)
{
errs += f();
errs += g();
}
void test2(void)
{
int localerrs = errs;
localerrs += f();
localerrs += g();
errs = localerrs;
}
编译结果如下。
test1
STMDB sp!,{v1,lr}
BL f
LDR v1,[pc, #L00002c-。-8]
LDR a2,[v1,#0]
ADD a1,a1,a2
STR a1,[v1,#0]
BL g
LDR a2,[v1,#0]
ADD a1,a1,a2
STR a1,[v1,#0]
LDMIA sp!,{v1,pc}
L00002c
DCD |x$dataseg|
test2
STMDB sp!,{v1,v2,lr}
LDR v1,[pc, #L00002c-。-8]
LDR v2,[v1,#0]
BL f
ADD v2,a1,v2
BL g
ADD a1,a1,v2
STR a1,[v1,#0]
LDMIA sp!,{v1,v2,pc}
从编译的结果中可以看出,test1中每次对全局变量errs的访问都会使用耗时的Load/Store指令;而test2只使用了一次内存访问指令。这对提高程序的整体性能有很大帮助。
3.指针链
指针链(Pointer Chains)常被用来访问结构体内部变量。下面的例子显示了一个典型的指针链的使用。
typedef struct { int x, y, z; } Point3;
typedef struct { Point3 *pos, *direction; } Object;
void InitPos1(Object *p)
{
p-》pos-》x = 0;
p-》pos-》y = 0;
p-》pos-》z = 0;
}
上面的代码每次使用“p-》pos”时都会对变量重新取值。为了提高代码效率,将程序改写如下。
void InitPos2(Object *p)
{
Point3 *pos = p-》pos;
pos-》x = 0;
pos-》y = 0;
pos-》z = 0;
}
经过改写的代码,减少了内存访问次数,提高程序的执行效率,另外也可以在object结构体中增加一个point3域,专门作为指向p-》pos的指针。
下载该资料的人也在下载
下载该资料的人还在阅读
更多 >
- VersaClock 6 系列寄存器描述和编程指南
- VersaClock 6 系列寄存器描述和编程指南
- STM32的寄存器操作
- C语言:寄存器操作
- C语言访问MCU寄存器
- LabVIEW初级教程之循环中的数据操作隧道和移位寄存器示例程序 18次下载
- Atmel SAMC21的I2C驱动寄存器操作和寄存器代码免费下载 19次下载
- FPGA视频教程之SF-EP1C开发板基于M4K块的移位寄存器配置仿真实验说明 8次下载
- APLL的编程和使用和和寄存器描述详细概述 6次下载
- TMS320F2812寄存器定义 13次下载
- HELLODSP中F2812寄存器速查 12次下载
- 浅谈ARM寄存器组织 1次下载
- 寄存器与移位寄存器 0次下载
- 多寄存器组网络处理器上的寄存器分配技术 28次下载
- 寄存器与移位寄存器
- CPSR寄存器和APSR寄存器的组成 3620次阅读
- 什么是编译器算法之寄存器分配 958次阅读
- ARM通用寄存器及状态寄存器详解 6334次阅读
- 不同思路的寄存器分配算法 1327次阅读
- C语言操作寄存器的常见手法 3159次阅读
- 鸿蒙内核源码中C7,C2,C13三个寄存器 3140次阅读
- 寄存器与内存的区别 9939次阅读
- 移位寄存器的原理 7.3w次阅读
- 技术 | FANUC数据寄存器和位置寄存器的运用介绍 1.6w次阅读
- 寄存器变量 2241次阅读
- 逆向基础之寄存器和内存详解 2902次阅读
- 移位寄存器怎么用_如何使用移位寄存器_移位寄存器的用途 1.9w次阅读
- 移位寄存器的特点_移位寄存器工作原理 4.8w次阅读
- 移位寄存器实验报告_移位寄存器原理 2.8w次阅读
- 闪存存储器是寄存器吗?_寄存器和存储器的区别 1.1w次阅读
下载排行
本周
- 1PC1013三合一快充数据线充电芯片介绍
- 1.03 MB | 5次下载 | 免费
- 2基于c51的时钟设计
- 0.56 MB | 3次下载 | 免费
- 3智能门锁原理图
- 0.39 MB | 3次下载 | 免费
- 4CC256x TI蓝牙协议栈基础HFGAGDemo应用
- 1006.09KB | 2次下载 | 免费
- 5通过C51单片机控制数码管显示
- 0.56 MB | 2次下载 | 免费
- 62024PMIC市场洞察
- 2.23 MB | 2次下载 | 免费
- 7CC256x TI Bluetooth Stack SPPLEDemo应用
- 1.48MB | 1次下载 | 免费
- 8电源拓扑快速参考指南
- 3.98MB | 1次下载 | 免费
本月
- 1XL4015+LM358恒压恒流电路图
- 0.38 MB | 146次下载 | 1 积分
- 2新概念模拟电路第四册信号处理电路电子书免费下载
- 10.69 MB | 65次下载 | 免费
- 3PCB布线和布局电路设计规则
- 0.40 MB | 23次下载 | 免费
- 4GB/T4706.1-2024 家用和类似用途电器的安全第1部分:通用要求
- 7.43 MB | 11次下载 | 1 积分
- 5JESD79-5C_v1.30-2024 内存技术规范
- 2.71 MB | 10次下载 | 免费
- 6elmo直线电机驱动调试细则
- 4.76 MB | 9次下载 | 6 积分
- 7串口工具UartAssist5.0.exe
- 0.60 MB | 8次下载 | 免费
- 8使用CR6850C设计并制作12V5A开关电源
- 1.53 MB | 6次下载 | 2 积分
总榜
- 1matlab软件下载入口
- 未知 | 935115次下载 | 10 积分
- 2开源硬件-PMP21529.1-4 开关降压/升压双向直流/直流转换器 PCB layout 设计
- 1.48MB | 420061次下载 | 10 积分
- 3Altium DXP2002下载入口
- 未知 | 233084次下载 | 10 积分
- 4电路仿真软件multisim 10.0免费下载
- 340992 | 191367次下载 | 10 积分
- 5十天学会AVR单片机与C语言视频教程 下载
- 158M | 183330次下载 | 10 积分
- 6labview8.5下载
- 未知 | 81581次下载 | 10 积分
- 7Keil工具MDK-Arm免费下载
- 0.02 MB | 73806次下载 | 10 积分
- 8LabVIEW 8.6下载
- 未知 | 65985次下载 | 10 积分
评论
查看更多