tbwshc

tbw

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  95 Posts :: 8 Stories :: 3 Comments :: 0 Trackbacks

常用链接

留言簿(4)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

事情应该尽可能简化,而不只是简单一点点,一爱因斯坦

虽然使软件正确的工作好像应该是一个工程合乎逻辑的最后一个步骤,但是在嵌入式的系统的开发中,情况并不总是这样的。出于对低价系列产品的需要,硬件的设计者需要提供刚好足够的存储器和完成工作的处理能力。当然,在工程的软件开发阶段,使程序正确的工作是很重要的。为此,通常需要一个或者更多的开发电路板,有的有附加的存贮器,有的有更快的处理器,有的两者都有。这些电路板就是用来使软件正确工作的。而工程的最后阶段则变成了对代码进行优化。最后一步的目标是使得工作程序在一个廉价的硬件平台上运行。

提高代码的效率
所有现代的 C 和C++编译器都提供了一定程度上的代码优化。然而,大部分由编译器执行的优化技术仅涉及执行速度和代码大小的一个平衡。你的程序能够变得更快或者更小,但是不可能又变快又变小。事实上,在其中一个方面的提高就会对另一方面产生负面的影响。哪一方面的提高对于程序更加的重要是由程序员来决定。知道这一点后,无论什么时候遇到速度与大小的矛盾,编译器的优化阶段就会作出合适的选择。

因为你不可能让编译器为你同时做两种类型的优化,我建议你让它尽其所能的减少程序的大小。执行的速度通常只对于某些有时间限制或者是频繁执行的代码段是重要的。而且你可以通过手工的办法做很多事以提高这些代码段的效率。然而,手工改变代码大小是一件很难的事情,而且编译器处于一个更有利的位置,使得它可以在你所有的软件模块之间进行这种改变。

直到你的程序工作起来,你可能已经知道或者是非常的清楚,哪一个子程序或者模块对于整体代码效率是最关键的。中断服务例程、高优先级的任务、有实时限制的计算、计算密集型或者频繁调用的函数都是候选对象。有一个叫作profiler 的工具,它包括在一些软件开发工具组中,这个工具可以用来把你的视线集中到那些程序花费大部分时间(或者很多时间)的例程上去。
一旦你确定了需要更高代码效率的例程,可以运用下面的一种或者多种技术来减少它们的执行时间。

inline 函数
在 c++中,关键字inline 可以被加入到任何函数的声明。这个关键字请求编译器用函数内部的代码替换所有对于指出的函数的调用。这样做删去了和实际函数调用相关的时间开销,这种做法在inline 函数频繁调用并且只包含几行代码的时候是最有效的。

inline 函数提供了一个很好的例子,它说明了有时执行的速度和代码的太小是如何反向关联的。重复的加入内联代码会增加你的程序的大小,增加的大小和函数调用的次数成正比。而且,很明显,如果函数越大,程序大小增加得越明显。优化后的程序运行的更快了,但是现在需要更多的ROM。

查询表
switch 语句是一个普通的编程技术,使用时需要注意。每一个由tb机器语言实现的测试和跳转仅仅是为了决定下一步要做什么工作,就把宝贵的处理器时间耗尽了。为了提高速度,设法把具体的情况按照它们发生的相对频率排序。换句话说,把最可能发生的情况放在第一,最不可能的情况放在最后。这样会减少平均的执行时间,但是在最差情况下根本没有改善。

如果每一个情况下都有许多的工作要做,那么也许把整个switch 语句用一个指向函数指针的表替换含更加有效。比如,下面的程序段是一个待改善的候选对象:

enum NodeType {NodeA, NodeB, NodeC}
switch(getNodeType())
{
case NodeA:
...
case NodeB:
...
case NodeC:
...
}
为了提高速度,我们要用下面的代码替换这个switch 语句。这段代码的第一部分是准备工作:一个函数指针数组的创建。第二部分是用更有效的一行语句替换switch 语句。

int processNodeA(void);
int processNodeB(void);
int processNodeC(void);
/*
* Establishment of a table of pointers to functions.
*/
int (* nodeFunctions[])() = { processNodeA, processNodeB, processNodeC };
...
/*
* The entire switch statement is replaced by the next line.
*/
status = nodeFunctions[getNodeType()]();

手工编写汇编
一些软件模块最好是用汇编语言来写。这使得程序员有机会把程序尽可能变得有效率。尽管大部分的C/C++编译器产生的机器代码比一个一般水平的程序员编写的机器代码要好的多,但是对于一个给定的函数,一个好的程序员仍然可能做得比一般水平的编译器要好。比如,在我职业生涯的早期,我用C 实现了一个数字滤波器,把它作为TI TMS320C30 数字信号处理器的输出目标。当时我们有的tb编译器也许是不知道,也许是不能利用一个特殊的指令,该指令准确地执行了我需要的那个数学操作。我用功能相同的内联汇编指令手工地替换了一段C 语言的循环,这样我就能够把整个计算时间降低了十分之一以上。

寄存器变量
在声明局部变量的时候可以使用 register 关键字。这就使得编译器把变量放入一个多用选的寄存器,而不是堆栈里。合适地使用这种方珐,它会为编译器提供关于最经常访问变量的提示,会稍微提高函数的执行速度。函数调用得越是频繁,这样的改变就越是可能提高代码的速度。

全局变量
使用全局变量比向函数传递参数更加有效率。这样做去除了函数调用前参数入栈和函数完成后参数出栈的需要。实际上,任何子程序最有效率的实现是根本没有参数。然而,决定使用全局变量对程序也可能有一些负作用。软件工程人士通常不鼓励使用全局变量,努力促进模块化和重入目标,这些也是重要的考虑。

轮询
中断服务例程经常用来提高程序的效率。然而,也有少数例子由于过度和中断关联而造成实际上效率低下。在这些情况中,中断间的平均时间和中断的等待时间具有相同量级。这种情况下,利用轮询与硬件设备通信可能会更好。当然,这也会使软件的模块更少。

定点运算
除非你的目标平台包含一个浮点运算的协处理器,否则你会费很大的劲去操纵你程序中的浮点数据。编译器提供的浮点库包含了一组模仿浮点运算协处理器指令组的子程序。很多这种函数要花费比它们的整数运算函数更长的执行时间,并且也可能是不可重入的。

如果你只是利用浮点数进行少量的运算,那么可能只利用定点运算来实现它更好。虽然只是明白如何做到这一点就够困难的了,但是理论上用定点运算实现任何浮点计算都是可能的。(那就是所谓的浮点软件库。)你最大的有利条件是,你可能不必只是为了实现一个或者两个计算而实现整个IEEE 754 标准。如果真的需要那种类型的完整功能,别离开编译器的浮点库,去寻找其他加速你程序的方法吧。


 

posted on 2013-07-23 17:19 tbwshc 阅读(275) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理