高效C编程(上) 基础数据类型,Loop,寄存器分配,函数调用,指针别名 ARM

来源:互联网 发布:如何入侵网站修改数据 编辑:程序博客网 时间:2024/06/05 05:23

导读:
本章是基于ARM讲述了如何在C编程中提高代码的效率。涉及到五部分:
第一部分:在局部变量中基础数据类型的使用。
第二部分:循环结构的优化
第三部分:Register allocation
第四部分:函数调用
第五部分:Pointer aliasing

1.基础C数据类型

本文讲述了C编译器遇到的一系列问题,在理解这些问题的基础上,写出更快运行速度和更小的尺寸的代码。

C优化和编译概述

  • 为了写出高效C代码,我们需要关注3个方面。1:C编译器保守的代码部分 2:C编译器所依赖的处理器架构的限制 3:特定C编译器的限制
  • 本文大部分涉及到前2个方面:这适用于ARM C compiler。而第三个方面是取决于compiler vendor and compiler revision.所以我们不过多讲述。

基础C数据类型

  • ARM处理器用于32位的寄存器以及32-bit data processing operations.ARM architecture is a RISC load/store architecture.in other words, you must load or store values from memory into registers before acting on them.ARM没有提供arthmetic or logical instruction to manipulate values in memory directly.
  • 在早期ARM架构中提供了hardware support 用于加载和存储unsigned 8-bit和unsigned or signed 32-bit values.
  • 在ARM中加载值转变为int类型是不需要额外的指令的。LDR等都是直接用于32Bit value的。8-bit 16-bit等都需要先转换为32-bit数据。此外int转换为smaller type does not cost extra instruction on a store.
  • Because ARM processor were not good at handling signed 8-bit or any 16-bit values.Therefore ARM C compiler define char to be an unsigned 8-bit value, rather than a signed 8-bit values in many other C compilers.编译器也提供了如果你想使char变为signed类型的,在gcc中可以加上 -fsigned-char开启。在armcc则是-zc
C data type Implementation char unsigned 8-bit byte—我在stm32上已经验证过 short signed 16-bit byte int signed 32-bit byte long signed 32-bit byte long long signed 64-bit double word

local variable type

  • 绝大多数ARM data processing operations are 32-bit only.所以要尽量避免使用short和char,因为这2种类型会产生不必要的转换类型的汇编代码而降低了效率。因此要尽可能的使用32-bit的数据类型,int, long,即使你需要复制一个8-bit,16-bit的值,也要尽可能用int or long.
short checkssum_v4(shor * data){    unsigned int i;    int sum = 0;    for(i = 0; i < 64; i++)    {        sum += *(data++);        //如果采用: (效率低的代码)        //short sum; sum = (short)(sum+data[i]);        //data[i]内部运算的时候就是用了ADD LDRH2个汇编代码,而这个表达式(sum+data[i]结果就是int类型的,所以还需要(short)(sum+data[i])    }    return (short)sum;}/*本段C代码,就是用了3个技巧来提高效率。1.首先循环的i使用了int而不是char之类的。2.其次避免使用data[i]的代码,通过*(data++)减少了一条用于找到&data[i]的汇编代码。3.最后,sum使用int在返回的时候才将其转为short,避免了运算过程中多余的转换。*/

Function Argument T ypes

  • 函数的参数在传参的时候也会面临转换的问题。gcc采用了严谨的做法,在进入函数的时候会将参数先转换为需要的参数类型。然后再进行处理。最合理的做法是函数参数也是使用int或者long。

unsigned versus signed types

在进行addition,subtraction and multiplication的时候signed和unsigned是一样的。在进行division的时候,使用unsigned的类型是更高效的。因为signed type在除法的时候因为有符号位,不能单纯的a >> 1,在是负数的时候要先+1,再进行>>1。然而unsigned value却没有这种问题。

Besides

当在main memory使用全局变量和数组entries的时候,使用smallest size type是更好的。尽可能采用显性的类型转换。

2.C循环结构

固定长度的循环

不高效的反面代码

for(i = 0; i < 64; i++);//在成为汇编时指令需要3个

高效代码

for(i = 64; i!=0 ;i++); //汇编指令会减少为2个(没有了需要与64比较的代码)
  • 有人会说可不可以将i!=0换成i>0。实际上后者是低效和不安全的。在实际中i!=0会等效于先i-1,再进行i!=0的比较。并且在遇到i=-0x80000000的时候,i-1会导致i=0x7fffffff,这在i>0的情况会导致循环没有终止,而i!=0则不会遇到这种情况

变量长度的循环

如果可以确定数组不为空,那么可以将while()形式变为do-while()形式,能够节省循环前判断N不为0的指令。

loop unrolling

在ARM7和9中,subtract需要1个cycle而branch需要3 cycle。在循环中展开代码,可以提高效率。
要点:
* 仅仅在unroll loop 对APP整体性能重要的时候才展开loop。此外,unroll会增加代码尺寸却只有一点性能的提高。并且,unroll通过占据了在cache中更重要的代码,甚至能降低性能。
* 另外,当loop body很大的时候,如果unroll只会提升1%的性能,却会增加很多空间消耗,这是非常不可取的。
* 如果要使用unroll loop我们最好要保证你展开的代码是可以整除的(可以是4或8的整数倍 。如果不能保证,则可以增加额外的代码来处理,比如下面的代码:

 for(i = N/4; i!=0; i--) {     sum += *(data++);     sum += *(data++);     sum += *(data++);     sum += *(data++); } for(i = N&3; i!=0; i--)//处理余下的代码 {     sum += *(data++); }

3.Register allocation

Efficient Register Alloaction
* 函数的内层循环最大的变量数量不要超过12个
* 尽量让最重要的变量在最内层的循环中使用

4.Function Calls

  1. 前4个int参数被放置在ARM的寄存器r0,r1,r2,r3中。剩下的参数会放入到full descending stack中。
  2. longlong和double 参数会存放在连续的2个寄存器中,返回值会存放在r0,r1中。
  3. C++中第一个参数是this指针,所以只剩下3个参数。如果C超过了4个参数,C++超过了3个,最有效的办法是将其整合到一个结构体中。
  4. 可以将C function(较小的)和其调用者放到同一个文件中。C compiler may inline the code in the caller。这样可以完全移除function call overhead. 此外也可以在较小的函数前使用inline关键字,这样可以提高效率。但是如果对较大的函数使用_inline会大幅度提高代码尺寸,从而降低效率。

5.Pointer aliasing

使用局部变量保存参数中指针所指向的值

  • 在函数中,编译器不知道哪个指针是别名的,哪个不是。所以会出现多次复制参数指针所指向的值。如下面的代码:
void timer(int * timer1, int * timer2, int *step){  *timer1 += *step;  *timer2 += *step;//这里你可能会觉得汇编指令仅仅复制了一次*step的值。实际上复制了2次*step的值//最高效的办法就是先用一个局部变量存放*step的值。}高效:void timer(int * timer1, int * timer2, int *step){  int temp = *step;  *timer1 += temp ;  *timer2 += temp ;}

使用局部变量保存局部变量的地址

int data;int sum = 0;...data = getdata(&sum); //要避免直接使用变量的地址。sum++;//因为compiler担心这里有pointer aliasing,所以会在每次改变sum值的时候,都会read and write sum form stack。这样效率很低下。

如果不得不使用局部变量的地址。最好先用一个变量来保存sum的地址,然后使用,比如:

int data;int sum = 0;int * sum_addr = &sum;...data = getdata(sum_addr ); //能有效提高效率sum++;
0 0
原创粉丝点击