织梦CMS - 轻松建站从此开始!

罗索

基于ARM的C级优化

jackyhwei 发布于 2010-04-01 17:14 点击:次 
刚接触ARM,提炼总结几点基于ARM平台的C级优化中值得注意的地方,其实某些点对基于其他DSP平台的优化也是值得借鉴的。
TAG:

1. 确定能用unsigned int的地方就不要用int声明变量。在紧凑循环体内声明int变量的最好方式是:
    register unsigned int variable_name;

2. 尽量转化除法表达式,消除除法运算
   Time (numerator / denominator) = C0 + C1* log2 (numerator / denominator)
     = C0 + C1 * (log2 (numerator) - log2 (denominator)).

3. 同上
    uint modulo_func1 (uint count)
    {
       return (++count % 60);
    }

    uint modulo_func2 (uint count)
    {
       if (++count >= 60)
      count = 0;
      return (count);
    }

4. 全局变量局部化: 如果函数使用了大量全局变量,则最好将全局变量赋值给局部变量,使之能被分配到固定寄存器。但这只能是在该全局变量在其他函数中没有被使用。(If a function uses global variables heavily, it is beneficial to copy those global variables into local variables so that they can be assigned to registers. This is possible only if those global variables are not used by any of the functions which are called.)
    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;
    }
  
    对于函数参数也同样(Even though *data may never change, the compiler does not know that anyfunc () did not alter it, and so the program must read it from memory each time it is used - it may be an alias for some other variable that is altered elsewhere. If we know it won't be altered, we could code it like this instead: )

    void func1( int *data )
    {
        int i;

        for(i=0; i<10; i++)
        {
              anyfunc( *data, i);
        }
    }
    <========>
    void func1( int *data )
    {
        int i;
        int localdata;

        localdata = *data;
        for(i=0; i<10; i++)
        {
              anyfunc ( localdata, i);
        }
    }


5. 避免寄存器耗尽导致的变量在DDR的spilling: The compiler spills the least frequently used variables first, so as to minimize the cost of spilling. Spilling of variables can be avoided by:

> 限制live变量的最大数目,通过以下办法可以达到:保持表达式简小精悍,且在函数中不要使用过多变量,将大函数拆分成小函数;

> 对频繁调用的变量使用寄存器: 告诉编译器,该变量会被频繁使用,所以应该定为最高优先级直接分配寄存器。但在某些情况下,即使这样定义的变量仍然可能会spilled.


6. 变量类型的确定

    尽量避免使用char或short定义布局变量,因为在存放时要缩减空间,在调用进寄存器时要做有符号或无符号的扩展,这都是通过移位来实现的,需要两次调用指令。如果定义成整型或无符号整型就避免了这个问题,这对于先load数据到本来变量,然后在本地处理的情况特别重要。即使输入输出是8-bit or 16-bit,同样值得按32-bit来处理。如下,结果相同,但第一个运行最快。
    int wordinc (int a)
    {
       return a + 1;
    }
    short shortinc (short a)
    {
        return a + 1;
    }
    char charinc (char a)
    {
        return a + 1;
    }


7. 指针的处理
   
    尽可能传递指向结构体的指针,而非结构体,后者整个结构体内容都会拷贝到堆栈再依次传递,非常慢。实际上传递一个指针就解决问题了
    void print_data_of_a_structure ( const Thestruct *data_pointer)
    {
        ...printf contents of the structure...
    }


8. 指针链的处理(指向结构体内结构体的成员的指针)

    typedef struct { int x, y, z; } Point3;
    typedef struct { Point3 *pos, *direction; } Object;
    void InitPos1(Object *p)
    {
       p->pos->x = 0;    //多次Load p->pos
       p->pos->y = 0;
       p->pos->z = 0;
    }
    <========>
    void InitPos2(Object *p)
    {
       Point3 *pos = p->pos; //只load一次p->pos,对于ARM,内部无stack,可以将Point3定义为register变量
       pos->x = 0;             //实际不指定registers关键词,ARM上也是将局部变量放在寄存器里面,所以要求
       pos->y = 0;             //局部变量少,且定义成32位最好。
       pos->z = 0;             //全局变量都是放在存储器(片外),需要用load/store来访问
    }


9. 条件表达式的处理

    if / else条件声明尽可能简单,类似的关系表达式组织成块,方便编译器将之条件化。如下
    int g(int a, int b, int c, int d)
    {
       if (a > 0 && b > 0 && c < 0 && d < 0)
       // grouped conditions tied up together//
          return a + b + c + d;
       return -1;
    }


10. Boolean 表达式及范围检查,如下

    bool PointInRectangelArea (Point p, Rectangle *r)
    {
       return (p.x >= r->xmin && p.x < r->xmax && p.y >= r->ymin && p.y < r->ymax);
    }
    <======>
    bool PointInRectangelArea (Point p, Rectangle *r)
    {
        return ((unsigned) (p.x - r->xmin) < r->xmax && (unsigned) (p.y - r->ymin) < r->ymax);
    }


11. switch() instead of if...else...

    if( val == 1)
        dostuff1();
    else if (val == 2)
        dostuff2();
    else if (val == 3)
        dostuff3();
    <=======>
    switch( val )
    {
        case 1: dostuff1(); break;

        case 2: dostuff2(); break;

        case 3: dostuff3(); break;
    }
 

12.二分法拆除多重if判断

    if(a==1) {
    } else if(a==2) {
    } else if(a==3) {
    } else if(a==4) {
    } else if(a==5) {
    } else if(a==6) {
    } else if(a==7) {
    } else if(a==8) {
    }
    <=======>
    if(a<=4) {
        if(a==1)     {
        } else if(a==2) {
        } else if(a==3) {
        } else if(a==4) {      
        }
    }
    else
    {
        if(a==5) {
        } else if(a==6) {
        } else if(a==7) {
        } else if(a==8) {
        }
    }

=============================================
    if(a<=4)
    {
        if(a<=2)
        {
            if(a==1)
            {
                /* a is 1 */
            }
            else
            {
                /* a must be 2 */
            }
        }
        else
        {
            if(a==3)
            {
                /* a is 3 */
            }
            else
            {
                /* a must be 4 */
            }
        }
    }
    else
    {
        if(a<=6)
        {
            if(a==5)
            {
                /* a is 5 */
            }
            else
            {
                /* a must be 6 */
            }
        }
        else
        {
            if(a==7)
            {
                /* a is 7 */
            }
            else
            {
                /* a must be 8 */
            }
        }
    }

==================================================================================================
Slow and Inefficient                                           Fast and Efficient
        c=getch();                                                     c=getch();
        switch(c){                                                     switch(c){
            case 'A':                                                      case '0':
            {                                                              {
                do something;                                                  do something;
                break;                                                         break;
            }                                                              }
            case 'H':                                                      case '1':
            {                                                              {
                do something;                                                  do something;
                break;                                                         break;
            }                                                              }
            case 'Z':                                                      case '2':
            {                                                              {
                do something;                                                  do something;
                break;                                                         break;
            }                                                              }
        }                                                              }


13. switch法 vs. 查表法

    char * Condition_String1(int condition) {  
      switch(condition) {                     //需要240 bytes
         case 0: return "EQ";
         case 1: return "NE";
         case 2: return "CS";
         case 3: return "CC";
         case 4: return "MI";
         case 5: return "PL";
         case 6: return "VS";
         case 7: return "VC";
         case 8: return "HI";
         case 9: return "LS";
         case 10: return "GE";
         case 11: return "LT";
         case 12: return "GT";
         case 13: return "LE";
         case 14: return "";
         default: return 0;
      }
    }

    char * Condition_String2(int condition) {          //需要72 bytes
       if ((unsigned) condition >= 15) return 0;
          return
          "EQ\0NE\0CS\0CC\0MI\0PL\0VS\0VC\0HI\0LS\0GE\0LT\0GT\0LE\0\0" + 3 * condition;
    }

14. 循环的处理(重点)
> 判断循环结束时,循环计数递减,不要递增;

> 循环内函数的优化----这个以前没试过
    for(i=0 ; i<100 ; i++)
    {
        func(t,i);
    }
    -
    -
    -
    void func(int w,d)
    {
        lots of stuff.
    }

    <=======>

    func(t);
    -
    -
    -
    void func(w)
    {
        for(i=0 ; i<100 ; i++)
        {
            //lots of stuff.
        }
    }

> 循环展开
对循环迭代次数确定且比较小的情况下,可以牺牲代码量,将循环展开,省去了每次对循环计数的判断和递增。而对于循环迭代次数不确定的情况,我们无法展开,但可以采用一定的策略,做一定程度的展开,比如一次循环计数判断和递增,处理8次循环迭代。具体一次循环处理的block大小,要依据具体机器的cache配置和大小来定,如下:
    //Example 1

    #include<STDIO.H>

    #define BLOCKSIZE (8)

    void main(void)
    {
    int i = 0;
    int limit = 33; /* could be anything */
    int blocklimit;

    /* The limit may not be divisible by BLOCKSIZE,
     * go as near as we can first, then tidy up.
     */
    blocklimit = (limit / BLOCKSIZE) * BLOCKSIZE;

    /* unroll the loop in blocks of 8 */
    while( i < blocklimit )
    {
        printf("process(%d)\n", i);
        printf("process(%d)\n", i+1);
        printf("process(%d)\n", i+2);
        printf("process(%d)\n", i+3);
        printf("process(%d)\n", i+4);
        printf("process(%d)\n", i+5);
        printf("process(%d)\n", i+6);
        printf("process(%d)\n", i+7);

        /* update the counter */
        i += 8;

    }

    /*
     * There may be some left to do.
     * This could be done as a simple for() loop,
     * but a switch is faster (and more interesting)
     */

    if( i < limit )
    {
        /* Jump into the case at the place that will allow
         * us to finish off the appropriate number of items.
         */

        switch( limit - i )
        {
            case 7 : printf("process(%d)\n", i); i++;
            case 6 : printf("process(%d)\n", i); i++;
            case 5 : printf("process(%d)\n", i); i++;
            case 4 : printf("process(%d)\n", i); i++;
            case 3 : printf("process(%d)\n", i); i++;
            case 2 : printf("process(%d)\n", i); i++;
            case 1 : printf("process(%d)\n", i);
        }
    }

    }

> 循环中带if判断的处理----比较多用于统计数据中特定bit的个数
    //Example - 1

    int countbit1(uint n)
    {
      int bits = 0;
      while (n != 0)
      {
        if (n & 1) bits++;
        n >>= 1;
       }
      return bits;
    }

    <=======>
    //Example - 2

    int countbit2(uint n)
    {
       int bits = 0;
       while (n != 0)
       {
          if (n & 1) bits++;
          if (n & 2) bits++;
          if (n & 4) bits++;
          if (n & 8) bits++;
          n >>= 4;
       }
       return bits;
    }

> 循环提前结束

    加 break;

15. 函数设计----原则是使函数小而简单,使得编译器可以自动完成各种优化,比如更有效的寄存器分配。

> 函数调用的代价---多余4个的函数参数会压栈,消耗大量的内存访问时间。这个问题的解决办法:
a. 尽量保证小函数参数不超过4个,避免参数压栈;
b. 如果函数确实需要4个以上的参数传递,尽量保证超过的参数起到的作用能补偿访问堆栈导致的损失;
c. 传递结构体指针,而不要传结构体本身;
d. 将相关的参数放入结构体,然后传递结构体指针。这可以减少参数数目并增加可读性;
e. 尽量减少long型参数个数,long型要占用2个参数的位置。对doubles型参数同理,如果需要处理浮点型 数据;
f. 避免函数的某个参数部分通过寄存器传,而部分又压入堆栈(split-argument),当前编译器对这种情况的处理非常低效:所有寄存器都会压入堆栈;
g. 避免函数带可变数目的参数,这种函数的参数都是通过堆栈传递。

> leaf function: 不再调用其他函数的函数
    大部分应用中,过半函数是调用的leaf function。对所有平台来说,leaf function均能获得最有效的编译,因为它们不需要做寄存器的保存和恢复。相比需要超过4个寄存器的leaf function完成的复杂工作,函数进出时的寄存器压栈和出栈消耗的代价非常小,由此,如果需要,应该尽可能频繁调用leaf function,而避免函数体过大,大量的寄存器变量压栈。以下方法确保函数按leaf function来编译:
a. 避免调用其他函数,包括对C库函数的调用;
b. 对被调用的函数使用内联_inline。

>inline 函数

    使用inline的几点优势:
    >>省去了函数调用的消耗,比如寄存器的保存和恢复。
    >>Lower argument evaluation overhead: 参数传递的代价通常比较小,因为它不需要做变量拷贝,如果有些参数是常数,则编译器会对目标代码做更深的优化。
    inline只适合用在关键小函数上。注意:如果inline使用得当,也可以减少代码量: 内联调用通常使用的指令比较少,但内联后代码优化后产生的指令可能会更少。

(jacky)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201004/8960.html]
本文出处:网络博客 作者:jacky
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容