DIY编程器网

 找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 949|回复: 0
打印 上一主题 下一主题

[待整理] 高效的C编程之:寄存器分配

[复制链接]
跳转到指定楼层
楼主
发表于 2014-10-10 07:23:40 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
        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个以上的指针指向同一个地址对象。所以编译器认为,对任何一个指针的写入都将会影响从任何其他指针的读出,但这样会明显降低代码执行的效率。这就是著名的“寄存器别名(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的指针。
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 分享分享 支持支持 反对反对
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|文字版|手机版|DIY编程器网 ( 桂ICP备14005565号-1 )

GMT+8, 2024-5-21 00:02 , 耗时 0.100825 秒, 18 个查询请求 , Gzip 开启.

各位嘉宾言论仅代表个人观点,非属DIY编程器网立场。

桂公网安备 45031202000115号

DIY编程器群(超员):41210778 DIY编程器

DIY编程器群1(满员):3044634 DIY编程器1

diy编程器群2:551025008 diy编程器群2

QQ:28000622;Email:libyoufer@sina.com

本站由桂林市临桂区技兴电子商务经营部独家赞助。旨在技术交流,请自觉遵守国家法律法规,一旦发现将做封号删号处理。

快速回复 返回顶部 返回列表