当前位置:首页 > 嵌入式 > 嵌入式教程
[导读]编译器一项很重要的优化功能就是对寄存器的分配。与分配在寄存器中的变量相比,分配到内存的变量访问要慢得多。所以如何将尽可能多的变量分配到寄存器,是编程时应该重点考虑的问题。

14.7寄存器分配

编译器一项很重要的优化功能就是对寄存器的分配。与分配在寄存器中的变量相比,分配到内存的变量访问要慢得多。所以如何将尽可能多的变量分配到寄存器,是编程时应该重点考虑的问题。

注意

当使用-g或-dubug选项编译程序时,为了确保调试信息的完整性,寄存器分配的效率比不使用-g或-dubug选项低很多。

14.7.1变量寄存器分配

一般情况下,编译器会对C函数中的每一个局部变量分配一个寄存器。如果多个局部变量不会交迭使用,那么编译器会对它们分配同一个寄存器。当局部变量多于可用的寄存器时,编译器会把多余的变量存储到堆栈。这些被写入堆栈需要访问存储器的变量被称为溢出(Spilled)变量。

为了提高程序的执行效率:

·使溢出变量的数量最少;

·确保最重要的和经常用到的变量被分配在寄存器中。

可以被分配到寄存器的变量包括:

·程序中的局部变量;

·调用子程序时传递的参数;

·与地址无关变量。

另外,在一些特定条件下,结构体中的域也可以被分配到寄存器中。

表14.1显示了当C编译器采用ARM-Thumb过程调用标准时,内部寄存器的编号、名字和分配方法。

表14.1 C编译器寄存器用法

寄存器编号

可选寄存器名

特殊寄存器名

寄存器用法

r0

a1

函数调用时的参数寄存器,用来存放前4个函数参数和存放返回值。在函数内如果将这些寄存器用作其他用途,将破坏其值。

r1

a2

r2

a3

r3

a4

r4

v1

通用变量寄存器

r5

v2

r6

v3

r7

v4

r8

v5

r9

v6或SB或TR

平台寄存器,不同的平台对该寄存器的定义不同

r10

v7

通用变量寄存器。在使用堆栈边界检测的情况下,r10保存堆栈边界的地址

r11

v8

通用变量寄存器。

r12

IP

临时过渡寄存器,函数调用时会破坏其中的值

r13

SP

堆栈指针

r14

LR

链接寄存器

r15

PC

程序计数器

从表14.1可以看出,编译器可以分配14个变量到寄存器而不会发生溢出。但有些寄存器编译器会有特殊用途(如r12),所以在编写程序时应尽量限制变量的数目,使函数内部最多使用12个寄存器。

注意

在C语言中,可以使用关键词register给指定变量分配专用寄存器。但不同的编译器对该关键词的处理可能不同,使用时要查阅相关手册。

14.7.2指针别名

C语言中的指针变量可以给编程带来很大的方便。但使用指针变量时要特别小心,它很可能使程序的执行效率下降。在一个函数中,编译器通常不知道是否有2个或2个以上的指针指向同一个地址对象。所以编译器认为,对任何一个指针的写入都将会影响从任何其他指针的读出,但这样会明显降低代码执行的效率。这就是著名的“寄存器别名(PointerAliasing)”问题。

注意

一些编译器提供了“忽略指针别名”选项,但这可能给程序带来潜在的bug。ARM编译器是遵循ANSI/ISO标准的编译器,不提供该选项。

1.局部变量指针别名问题

通常情况下,编译器会试图对C函数中的每一个局部变量分配一个寄存器。但当局部变量是指向内存地址的指针时,情况有所不同。先来看一个简单的例子。

voidadd(int*i)

{

inttotal1=0,total2=0;

total1+=*i;

total2+=*i;

}

编译后生成:

add:

0000807CE3A01000MOVr1,#0

>>>POINTALIAS\#3inttotal1=0,total2=0;

00008080E3A02000MOVr2,#0

>>>POINTALIAS\#5total1+=*i;

00008084E5903000LDRr3,[r0,#0]

00008088E0831001ADDr1,r3,r1

>>>POINTALIAS\#6total2+=*i;

0000808CE5903000LDRr3,[r0,#0]

00008090E0832002ADDr2,r3,r2

>>>POINTALIAS\#8}

00008094E12FFF1EBXr14

>>>POINTALIAS\#11{

注意程序中i的值被装载了两次。因为编译器不能确定指针*i是否有别名存在,这就使得编译器不得不增加一条额外的Load指令。

另一个问题,当在函数中要获得局部变量地址时,这个变量就被一个指针所对应,就可能与其他指针产生别名。为了防止别名发生,在每次对变量操作时,编译器就会从堆栈中重新读入数据。考虑下面的例子程序,分析其产生的编译结果。

voidf(int*a);

intg(inta);

inttest1(inti)

{f(&i);

/*nowuse’i’extensively*/

i+=g(i);

i+=g(i);

returni;

}

编译结果如下所示。

test1

STMDBsp!,{a1,lr}

MOVa1,sp

BLf

LDRa1,[sp,#0]

BLg

LDRa2,[sp,#0]

ADDa1,a1,a2

STRa1,[sp,#0]

BLg

LDRa2,[sp,#0]

ADDa1,a1,a2

ADDsp,sp,#4

LDMIAsp!,{pc}

从上面代码的编译结果可以看出,对每一次i操作,编译器都将会从堆栈中读出其值。这是因为,一旦在函数中出现对i的取值操作,编译器就会担心别名问题。为了避免这种情况,尽量不要在程序中使用局部变量地址。如果必须这么做,那么可以在使用之前先把局部变量的值复制到另外一个局部变量中。下面的程序是对test1函数的优化。

inttest2(inti)

{

intdummy=i;

f(&dummy);

i=dummy;

/*nowuse’i’extensively*/

i+=g(i);

i+=g(i);

returni;

}

编译后的结果如下。

test2

STMDBsp!,{v1,lr}

STRa1,[sp,#-4]!

MOVa1,sp

BLf

LDRv1,[sp,#0]

MOVa1,v1

BLg

ADDv1,a1,v1

MOVa1,v1

BLg

ADDa1,a1,v1

ADDsp,sp,#4

LDMIAsp!,{v1,pc}

从编译结果可以看出,修改后的代码只使用了2次内存访问,而test1为4次内存访问。

总上所述,为了在程序中避免指针别名,应该做到:

·避免使用局部变量地址;

·如果程序中出现多次对同一指针的访问,应先将其值取出并保存到临时变量中。

2.全局变量

通常情况下,编译器不会为全局变量分配寄存器。这样在程序中使用全局变量,很可能带来内存访问上的开销。所有尽量避免在循环体内使用全局变量,以减少对内存的访问次数。

如果在一段程序体内大量使用了同一个全局变量,建议在使用前先将其拷贝到一个局部的临时变量中,当完成对它的全部操作后,再将其写回到内存。

比较下面两个完成同样功能的函数,分析全局变量的操作对程序性能的影响。

intf(void);

intg(void);

interrs;

voidtest1(void)

{

errs+=f();

errs+=g();

}

voidtest2(void)

{

intlocalerrs=errs;

localerrs+=f();

localerrs+=g();

errs=localerrs;

}

编译结果如下。

test1

STMDBsp!,{v1,lr}

BLf

LDRv1,[pc,#L00002c-.-8]

LDRa2,[v1,#0]

ADDa1,a1,a2

STRa1,[v1,#0]

BLg

LDRa2,[v1,#0]

ADDa1,a1,a2

STRa1,[v1,#0]

LDMIAsp!,{v1,pc}

L00002c

DCD|x$dataseg|

test2

STMDBsp!,{v1,v2,lr}

LDRv1,[pc,#L00002c-.-8]

LDRv2,[v1,#0]

BLf

ADDv2,a1,v2

BLg

ADDa1,a1,v2

STRa1,[v1,#0]

LDMIAsp!,{v1,v2,pc}

从编译的结果中可以看出,test1中每次对全局变量errs的访问都会使用耗时的Load/Store指令;而test2只使用了一次内存访问指令。这对提高程序的整体性能有很大帮助。

3.指针链

指针链(PointerChains)常被用来访问结构体内部变量。下面的例子显示了一个典型的指针链的使用。

typedefstruct{intx,y,z;}Point3;

typedefstruct{Point3*pos,*direction;}Object;

voidInitPos1(Object*p)

{

p->pos->x=0;

p->pos->y=0;

p->pos->z=0;

}

上面的代码每次使用“p->pos”时都会对变量重新取值。为了提高代码效率,将程序改写如下。

voidInitPos2(Object*p)

{

Point3*pos=p->pos;

pos->x=0;

pos->y=0;

pos->z=0;

}

经过改写的代码,减少了内存访问次数,提高程序的执行效率,另外也可以在object结构体中增加一个point3域,专门作为指向p->pos的指针。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭