您的位置:首页 > 其它

现代嵌入式计算 - 第18章 - 性能优化 (第一部分)

2016-09-08 16:14 295 查看

18. 性能优化

性能调优是嵌入式系统开发的黑色艺术之一。项目开发过程中,通常在后期,也是产品即将交付的时期,需要对系统进行优化,对性能调优。而这个时期,也是时间最紧迫,压力最大的阶段。
庆幸的是,帮助就在眼前。我们已经总结了一系列有效的性能优化技巧和方法。这些最佳的方法都以模式的形式呈现给大家。这些模式分以下几类。

一般方法

代码和设计

处理器部分

网络技术

什么是模式?

每个性能改进方法记录在一个模式里。每个模式包含这些内容:


名字 -
模式名字

上下文-
该问题所处的相关环境和约束

问题 -
具体的要解决的问题

解决方案 -问题的解决方案。许多问题可以有多个解决方案,哪个方案更合适,取决于上下文。某些解决方案可能会偏重部分关键点,也就忽略了其它关键点。
•关键点 -在选择一个合适的解决方案时,经常需要在这些关键点中权衡。

模式语言是多个模式的组合。在一个模式中引用其它模式,写作”见模式 – 模式名“。
您不需要精读每一个模式,在您的性能优化任务里,也不必应用每个模式。然而,通读这些模式出现的上下文环境和问题的陈述有助于您全面了解这个模式语言提供了哪些手段。

一般方法

这组模式是一些通用方法和工具,这些模式不针对任何特定处理器或应用。

1. 定义性能目标

上下文:您是一个软件工程师,正准备对应用或某个驱动进行性能优化。

问题:性能改进工作可以成为一个永无止境的任务。如果没有目标,这个过程不能明确结束。

解决方法:在项目或客户接触的初期阶段,定义一个具体的,现实的,和可衡量的性能要求。
• “让它尽可能快”并不是一个具体的性能要求。
•“用一个600Mhz的CPU,要求对即使64字节的数据包,吞吐量可达10Gb”不是一个现实的目标。

关键点:

性能目标是很难设定的。
•性能不够可能会影响您的产品的竞争力。

性能目标是变化的;竞争对手不固步自封。新的竞争者也在不断的入场。
•如果没有目标,性能改进工作会很不明确。

2. 性能设计

上下文:您正在开发一个系统。您有一个可衡量的性能要求(见模式-定义性能目标)。

问题:系统设计无法满足性能需求,我们不希望在项目后期看到这样的情况。

解决方法:在设计时,描述主要的数据路径场景。在设计研讨会上,演示这个路径,并且写入项目的设计文档。在设计时,要清楚整个数据通路中时钟周期的消耗。在系统划分模块时,清楚每个模块对时钟周期的消耗。当来到代码检查阶段,一定要检查主要的数据路径。
设计阶段,分析应用的带宽需求,估计出瓶颈是在CPU,或者内存的带宽和延迟上。如果条件允许,可以在此阶段建立性能模拟环境。

关键点:
•设计时,有些系统瓶颈可能很难预测到。
•如果系统的设计无法满足性能的需求,这就很糟糕了,尤其在项目后期,那时就太晚了。

3. 避免过早的代码调优

上下文:您正在实施一个系统,而您是在项目的编码阶段。您有一个性能设计(请参见性能设计),了解性能需求。

问题:开始编写代码后,过多关注性能相关的代码细节会陷入复杂境地。

解决方法:找到性能,功能和可维护性之间的平衡关系是很重要的。一些研究已经发现,代码的20%消耗80%的执行时间;
4%的代码消耗50%的时间。
保持代码简单明了。简单的设计更容易优化。编译器也更容易优化简单的代码。如果您是负责系统的某个模块,您应该清楚这个模块的性能预算(见模式–
性能设计)。在单元测试中,您可以对您的数据路径的进行性能测试。在集成阶段,团队可以对整个数据路径进行性能评估。

关键点:
•高效的代码未必是“好”的代码。它可能很难理解和维护。
•在整个系统能够运行之前,很难找到真正的性能瓶颈。
•如果您花太多时间在初始编码过程中做微的优化,您可能会错过重要的全局优化。

4. 一步步记录

上下文:您正在尝试一些优化来解决特定的瓶颈。同时系统还存在其他一些瓶颈。

问题:当您在快节奏地做着优化工作时,有时很难记住数天前所实验的方法和结果。

解决方案:做好记录,记录每次定位瓶颈的实验,每次为提高性能而进行的优化。这些记录在后面可能是无价的。查看您的优化笔记可帮助您找到不正确的路径,或错误的假设。当性能改进工作完成时,它也是非常有用的,它可以帮助其他工程师从您的经验中获益。

关键点:

做记录有时会打断工作以及思路。

5. 扣篮优化

上下文:您已经做了一些改进,增加了代码的运行效率。

问题:最新的优化并没有提高性能。您已经意识到一些不明的限制因素。这些瓶颈可能来自环境因素,协议或者测试设备。

解决方案:选择一个明确可以提高性能的修改方法,比如:
•暂时去除数据通道上的诸如IP校验算法。

提高处理器的时钟频率。

这个可以明确提高性能的方法并没有提高性能,这样的结果指向某个隐藏的瓶颈,找到这个瓶颈,而后恢复之前的系列优化。
我们曾经在一个应用中,实施了多种优化应该具有改进性能的功效,但没有。然后,我们删除了IP校验,当然性能依然没增加。这种结果表明有一个隐藏的限制因素,一个未知的瓶颈。当我们深入调查,发现我们配置物理层设备出了问题,当我们解决了这个隐藏限制因素,性能约25%提升。追溯并应用之前的步骤,性能都得到明显提升。

6. 应用最佳的编译器

上下文:您有编译器的的选择余地

问题:不同的编译器生成的代码有不同的性能表现。您需要选择最适合您应用和平台的编译器。

解决方案:尝试并选择表现最好的编译器。GCC是常用的性能较好的编译器,然而,芯片方案商通常会提供高度优化后的处理器。对于特定应用,好的编译器通常能提升性能5~10%。

关键点:

有些编译器相当昂贵。
•有些编译器和操作系统可能不匹配。例如,您想使用的编译器生成错误的目标文件格式。
•某个编译器可能在某方面优化效果良好,但不能保证它会以同样的方式优化您的应用。
•如果您采用Linux内核,您可能必须使用GCC。内核某些部分使用了GCC的特定扩展。

7. 编译器优化

上下文:您已选择C编译器(请参阅模式-应用最佳的编译器)。

问题:您没有启用编译器优化选项。

解决方案:您的编译器支持多种优化开关。使用这些选项可以增加应用程序的全局性能,这也相当亲而易举。阅读您的编译器文档,了解这些选项。
一般情况下,最高级别的优化开关是-O开关。在GCC里,这个开关带一个参数。找出编译器的最大参数值并且使用它。典型的编译器支持三个层次的优化。尝试的最高级别,在GCC中,最高级别是-O3。然而,在过去-O3代码生成了更多的错误,而-O2成为最常用的优化级别。Linux内核编译时用-O2。如果您用-O3有问题,则可能需要恢复到-O2。在一个应用测试中,从-O2到-O3提升了15%的包处理性能。然而另一个应用中,-O3却比-O2慢些。您可以限制编译器优化单个C源文件。引进优化标志,一个接一个去找对性能有帮助的选项。
其他GCC优化参数,可以提高性能的有,
• -funroll-loops
•-fomit-frame-pointer

关键点:
•一般情况下,优化增加生成的代码大小。

某些优化可能不会提高性能。
•编译器支持大量的开关和选项。它可以是耗时冗长的文档。

优化的代码难以调试。
•某些优化可能触发编译器的错误,因为它们不象常用的默认选项。
•启用优化可能改变你代码的时序,可能带来一些潜在的问题。

8. 数据缓存

上下文:您正在使用一个包含数据高速缓存的处理器。缓存比内存和外设要快很多。

问题:处理器花费大量的时间等待外部存储器上的数据,降低了性能。您可以使用性能监测函数得到处理器等待内存数据的指令周期,从而确定这个问题。
在一些应用中,我们可以显著地观察到大量的指令周期用在数据等待中。

解决方案:
在一般情况下,用于访问存储器的最有效的机制是使用数据高速缓存。核心访问高速缓存比访问DRAM快好几倍,而且,它不需要使用内部/外部总线,留下给其它设备使用,比如高速I/
O。
高速缓存单元可以有效地利用存储器总线的。在IA-32处理器,CPU核心在特殊的内存访问时序上抓取一个缓存行(64字节)。这是远比32位数据读取要有效得多。
高速缓存支持多种功能,满足您灵活定制系统的需要。关键是要了解这些功能的影响,以及如何微调它们用于特定的应用。后面的章节覆盖了许多高速缓存相关的功能。
在一个应用程序中(嵌入式系统),不缓存描述符和包数据的开发人员,在启用高速缓存后,得到了25%的包处理性能提升。选择适合于数据缓存的数据结构。例如,栈通常比链表数据结构更有效利用高速缓存。
大多数应用程序,指令高速缓存是非常有效的。我们值得花时间优化使用的数据缓存。

关键点:
•如果CPU核心与其他总线主控共享你要缓存的内存数据,IA-32系统会保持缓存和I/O的一致性,然而,一些其他CPU架构,您需要显示地刷新/更新缓存。
•如果使用缓存,最好是确保两个不同的数据集不共用相同的缓存行。
疏忽的缓存共享,俗称假的缓存共享,出现在两个端点(CPU线程或IO设备)试图访问同一高速缓存行。在这种情况下,必须解决缓存冲突,从而导致性能下降。
•要注意缓存的内容。时间局部性指的是,两次访问数据的时间间隔。如果访问数据片段一次或不常访问它,它具有低的时间局部性。如果你缓存这种数据,缓存换出算法可能会导致性能敏感的数据被这个低优先级的数据驱逐。
•处理器执行一个轮询调度的缓存替换算法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  嵌入式 性能优化