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

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的指针。

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

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭