Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2207680
  • 博文数量: 436
  • 博客积分: 9833
  • 博客等级: 中将
  • 技术积分: 5558
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-29 10:27
文章存档

2013年(47)

2012年(79)

2011年(192)

2010年(118)

分类: C/C++

2010-12-01 11:38:51

   我们在上次的基础上添加了一些内容。看到老师的资料,发现高可靠部分的内容还是不少,不多大多都是在硬件方面或是理论知识,编程技术方面的内容比较少。在网上看到“MISRA--工业标准的C编程规范”,其中很大一部分规则是为了提高代码的可靠性,但是苦于没有完整的中文版,英文版看起来非常费力,所以在这次的文章中没有添加“工业标准”的相关内容。
    上次上传的文件貌似是非法的,所以看不到,这次直接贴上。
 
第一章 降低系统功耗,提高程序效率
1. 变量
1.1定义
在进行实际程序开发时,变量的使用至关重要,其中使用全局变量比向函数传递参数更加有效,这样免去了函数调用时参数人栈和出栈的需要。当然,使用全局变量会对程序有一些副作用。变量定义的次序会导致最终映像中数据布局的不同,如图1所示。
1
由此可见,在声明变量时,需要考虑怎样最佳地控制存储器布局。最好的方法是在编程的时候,把所有相同类型的变量放在一起定义。通常,工程师设法使用short或char来定义变量以节省存储器空间。在函数的局部变量数目有限的情况下,编译器会把局部变量分配给内部寄存器,每个变量占用一个寄存器。在这种情况下,使用short和char型变量不但不会节省空间,反而会带来其它的副作用。
图2
如图2所示:假定a是任意可能的寄存器,存储函数的局部变量。同样完成加1的操作,32位的int型变量最快,只用一条加法指令。而8位和16位变量,完成加法操作后,还需要在32位的寄存器中进行符号扩展。其中,带符号的变量,要用逻辑左移和算术右移两条指令才能完成符号扩展;无符号的变量,要使用一条逻辑与指令对符号位进行清零。所以,使用32位的int或unsigned int局部变量最有效。某些情况下,函数从外部存储器读入局部变量进行计算,这时候,需要把不是32位的变量转换成32位。至于把8位或16位变量扩展32位后,隐藏了原来可能溢出异常这个问题,需要进一步仔细考虑。
通常,工程师总是竭力避免使用冗余变量, 以精简程序。一般情况下这样做是正确的,但是也有例外,如下所示:
int f(void);
int g(void);
//f0和gO不访问全局变量errs
int errs; //全局变量
void test1(void)
{ers+=f();
ers+=g();
}
void test2(void)
{int localerrs=errs;
//定义冗余的局部变量
localers+=f();
localers+=g();
ers= localers;
)
在第一种情况testl0里,每次访问全局变量ers时都要先从相应的存储器下载到寄存器里,经f()或g()函数调用后再存储回原来的存储器里面。在该例子中,一共要进行两次这样的下载/存储操作。而在第二种情况test20里,局部变量localers被分配以寄存器,这样一来,整个函数就只需要一次下载/存储全局变量存储器了。尽量节省存储器访问的次数,对于提高系统性能非常有用。
1.2寄存器变量
在声明局部变量的时候可以使用register关键字。这就使得编译器把变量放入一个多用途的寄存器中.而不是在堆栈中,合理使用这种方法可以提高执行速度。函数调用越是频繁。越是可能提高代码的速度。
2. 适当使用宏
在嵌入式系统中,当函数较短而传递参数较多的情况下,为了能达到性能要求,宏是一种很好的代替函数的方法。在C语言中宏是产生内嵌代码的唯一方法,是实现类似函数功能而又不具函数调用和返回开销的较好方法,但宏在本质上不是函数,因而要防止宏展开后出现不可预料的结果,对宏的定义和使用要慎而处之。
由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:
#define ceil_div(x,Y)(x+Y一1)/Y
a=eeil_div(b&e,sizeof(int));
将被转化为:
a=(b&e+sizeof(int)一1)/sizeof(int);
//由于+/.的优先级高于&的优先级,那么上面式子等同于:
a=(b&(e+sizeof(int)一1)),sizeof(inO;
这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:
#define eeil_div(x,Y)(((x)+(y)一1)/(y))
比如:宏在展开的时候对其参数可能进行多次取值,但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果。甚至会发生更严重的错误。比如:
#define min(X,Y)((x)>(Y)?(Y):(X))
e=min(a,foo(b));
这时foe()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:
#define min(X,Y)({
typeof(X)X一=(x);
typeof(Y)Y一=(Y);
(X一
(k})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部范围)。
3.     数组
在数组寻址中,尽量使用2的幂次方,如图
同时,尽量使用一维数组,因为它的寻址效率要比多维数组高,而且在遍历时,一维数组也较多维数组使用更少的指令,因而效率也更高。
4.     switch/case
在程序中,经常会使用switch/case语句,每一个由机器语言实现的测试和跳转仅仅是为了决定下一步要做什么,就浪费了处理器时间。为了提高速度,可以把具体的情况按照它们发生的相对频率排序。即把最可能发生的情况放在第一,发生概率小的情况放在最后,这样会减少代码平均执行时间。
5.     if/else
将计算速度最快的表达式放在左边,当结果确定后,不再有其他需要计算的表达式,也就是“短路”,如:if((strlen(a)>100||(b>100))。
6.     循环处理
6.1循环体的确定
6.2尽量使用自减计数
计数循环是程序中常用的流程控制结构。在c中,类似下面的for循环比比皆是:for(1oop=1;loop<=1imit;loop++)这种累加计数的方法符合一般的自然思维习惯,所以比下面的递减计数方法使用更多:for(1oop<=limit;loop! =();loop--)这两者在逻辑上并没有效率差异,但是映射到具体的体系结构中,就产生了很大的不同。累加法比递减法多用了一条指令,当循环次数比较多的时候,这两段代码就会在性能上产生明显的差异。分析其本质原因,在于当进行一个非零常数比较时,必须用专门的CMP指令来执行;而当一个变量与零进行比较时,ARM指令可以直接利用条件执行的特性(NE)来进行判断。很多时候循环展开由编译器自动完成,不过应注意对中间变量或结果被更改的循环,编译程序往往拒绝展开,这时候就需要工程师自己来做展开工作了。尤其值得注意的是,在有内部指令cache的CPU上(如ARM946ES芯片),因为循环展开的代码很大,往往会出现高速缓冲存储器溢出。这时展开的代码会频繁地在CPU的高速缓冲存储器和内存之间来回调用,又因为高速缓冲存储器速度很高,所以此时循环展开反而会变慢。同时,循环展开会影响矢量运算优化。ARM处理器核对NZ(零比较转移)有特别的指令处理,速度非常快,如果你的循环对方向不敏感,可以由大向小循环。需要注意的是,如果指针操作使用了i值,这种方法可能引起指针索引超界的严重错误(i=MAX+1)。当然你可以通过对i做加减运算来纠正,但是如果这样就没有提高效率的作用了。
6.3while循环和do/while循环
用while循环时有以下两种循环形式:
Unsigned int i;
i=0:
while(i<1000)
{
i++;//用户程序
}
或:
unsigned int i;
i=1000:
do
i--;//用户程序
while(i>0);
在这两种循环中,使用do⋯wmle循环编译后生成的代码的长度短于while循环。
7. 内嵌汇编
程序中对时间要求苛刻的部分可以用内嵌汇编处理,即在C程序中直接插入_asm{}内嵌汇编语句,以带来速度上的显著提高。但是,开发和测试汇编代码是一件辛苦的工作,它将花费更长的时间,因而要慎重选择要用汇编的部分。
C程序能够与汇编程序实现方便灵活的接口,在C程序中调用汇编十分方便灵活,对二者调用的主要难度在于:实现数据的准确传输。为了使单独编译的C语言程序和汇编语言程序能相互调用,定义了统一的函数过程调用标准ATPCS(ARM—Thumb Procedure Call Standard)。ATPCS定义了寄存器组中的R0~R3作为参数传递和结果返回寄存器。如果数目超过4个;则使用堆栈传递。因为内部寄存器的访问速度远远大于存储器,所以尽量使参数传递在寄存器中进行,即尽量把函数的参数控制在4个以下。
8. 利用硬件特性
由于CPU对各种存储器的访问速度是不同的,其速度基本上是:
CPU内部RAM>外部同步RAM>外部异步RAM>FLASH,ROM
对于程序代码,已经被烧录在FLASH或ROM中,我们最好在系统启动后将FLASH或ROM中的目标代码拷贝入RAM中后再执行以提高取指令速度;对于UART等设备;其内部有一定容量的接收BUFFER,我们应尽量在BUFFER被占满后再向CPU提出中断,从而避免浪费中断处理时间;如果对某设备能采取DMA方式读取,就采用DMA读取,DMA读取方式在读取目标中包含的存储
信息较大时效率较高,其数据传输的基本单位是块,而所传输的数据是从设备直接送入内存的(或者相反)。DMA方式较之中断驱动方式,减少了CPU对外设的干预,进一步提高了CPU与外设的并行操作程度。
9. 运算
9.1自加、自减
通常使用自加、自减指令和复合赋值表达式(如a-=1及a+=1等)都能够生成高质量的程序代码.编译器通常都能够生成inc和dec之类的指令,而使用a=a+1或a=a-1之类的指令,有很多C编译器都会生成二到三个字节的指令。在AVR单片适用的ICCAVR、GCCAVR、IAR等C编译器以上几种书写方式生成的代码是一样的。也能够生成高质量的im和d∞之类的的代码。
9.2位操作
使用C语言的位操作可以减少除法和取模的运算。在计算机程序中数据的位是可以操作的最小数据单位,理论上可以用”位运算”来图3不同循环条件比较完成所有的运算和操作,因而,灵活的位操作可以有效地提高程序运行的效率。比如:
//方法1
int i,j;
i=879/16;
j=562%32;
//方法2
int i,j;
i=879>>2;
j=562-(562>>5<<5);
对于以2的指数次方为”*”、”/¨或”%”因子的数学运算,转化为移位运算”<<>>”通常可以提高算法效率。因为乘除运算指令周期通常比移位运算大。
C语言位运算除了可以提高运算效率外,在嵌入式系统的编程中,它的另一个最典型的应用是使用位间的与(&)、或(|)、非(~)操作对硬件寄存器进行位设置。
9.3平方运算
例如:a=pow(a,2.0)可以改为:a=a*a
说明:在有内置硬件乘法器的单片机中,乘法运算比求平方运算快得多,因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的单片机中,如ATMegal63中,乘法运算只需2个时钟周期就可以完成。既使是在没有内置硬件乘法器的单片机中.乘法运算的子程序比平方运算的子程序代码短,执行速度快。如果是求3次方。如:a=pow(a,3.0)更改为a=a*a*a则效率的改善更明显。
9.4查表
在程序中一般不进行非常复杂的运算,如浮点数的乘除及开方等.以及一些复杂的数学模型的插补运算,对这些即消耗时间又消费资源的运算,应尽量使用查表的方式。并且将数据表置于程序存储区。
9.5浮点运算
Ø       将浮点运算转换为乘除运算。
Ø       优先使用浮点处理器,如果系统不支持浮点处理器,使用glibc提供的软浮点。
Ø       对于计算密集型的算法,我们可以通过转化为查表来进行优化。
Ø       在保证计算精度的前提下,确定浮点型变量和表达式是float型。
10.            线程
Ø       创建线程是有代价的,如果只让线程做很少的事,而又频繁的创建和销毁线程,是得不偿失的。
Ø       使用异步IO,来取代多线程+同步IO方式。
Ø     使用线程池,取代线程的创建和销毁。
11.            文件
Ø       读写文件,缓冲区的buffer为4096 2048时最快。
Ø       利用mmap,读写文件
利用mmap实现的一个文件拷贝的例子其基本流程是
1.创建一个与源文件大小相同的目标文件;
2.使用mmap,分别将源文件和目标文件映射到内存中;
3.使用memcpy,将文件读写操作,转化为内存的拷贝操作。
12.             
第二章 减少程序安全隐患,增加系统可靠性
1.   临时变量
Ø       不要对临时变量做取地址操作,因为你不知道编译器是否将这个变量映射到了寄存器。
Ø       不要返回临时变量的地址,或临时指针变量,因为堆栈中的内容是不确定的(出了这个函数,放在堆栈中的局部变量就没有意义了)。
Ø       不要在申请大的临时变量数组,因为临时变量是在堆栈中实现的。
char *DoSomething(…)
{
char i[32*1024];   //临时变量是通过堆栈实现的,太大的临时变量数组会冲掉堆栈。
  memset(I,0.32*1024);
  …
return i;          //返回堆栈中的地址是非常危险的,因为堆栈中的值永远是不确定的。
 
}
 
2.   动态内存
Ø       总是检查动态内存分配是否成功后再引用该指针。
Ø       在分配struct空间时,使用sizeof。
Ø       分配内存时宁滥勿缺(别忘了加1)。
Ø       总是free由malloc()函数返回的指针。
Ø       错误处理时不要忘了其他分配空间的释放。
char *DoSomething(…)
{
  char *p,*q;
  if((p=malloc(1024))==null) return null;
if((q=malloc(2048))==null) return null;        
//如果q没有申请到,应先释放p,再返回null
return p;
}
 
 
 
将if(a==b && c==d && e==f){…}改为
if(a==b)
{
 if(c==d)
{
  if(e==f)
{
 …
}
}
}
 
 
阅读(1618) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-12-01 15:26:00

广泛收集(wiki,blog,paper in english or chinese),全面归纳。 针对2-3个,做一些小实验,用实验数据说明优化前后的差异。 xuyuanchao