高效的C编程之:寄存器分配(中)

来源:本站
导读:目前正在解读《高效的C编程之:寄存器分配(中)》的相关信息,《高效的C编程之:寄存器分配(中)》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《高效的C编程之:寄存器分配(中)》的详细说明。
简介:编译器一项很重要的优化功能就是对寄存器的分配。与分配在寄存器中的变量相比,分配到内存的变量访问要慢得多。所以如何将尽可能多的变量分配到寄存器,是编程时应该重点考虑的问题。(EEFOCUS)

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)

{

提醒:《高效的C编程之:寄存器分配(中)》最后刷新时间 2024-03-14 01:07:25,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《高效的C编程之:寄存器分配(中)》该内容的真实性请自行鉴别。

上一篇高效的C编程之:寄存器分配(下)

下一篇高效的C编程之:寄存器分配(上)