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

来源:互联网 发布:java 正则替换 编辑:程序博客网 时间:2024/06/06 12:40

译自Modern Embedded Computing. by Peter Barry, Patrick Crowley

代码和设计

本节介绍适用于大多数处理器的一些通用的代码优化方法。在很多情况下,这些优化会减少可读性,可维护性,和移植性。确保你是优化需要优化的代码(见模式– 避免过早的代码优化)。


9. 重新排列结构体

上下文:你已经定位性能瓶颈在数据路径上的某段代码。代码采用了大的结构体。

问题:该结构跨越若干高速缓存行。


解决方案:重新排序结构里的字段,使得经常访问的领域放在一起。如果所有的访问域在一个缓存行上,那么首次访问将把整行刷新到缓存中。

有些架构是对地址对齐敏感的。只要有可能,使用编译器编译指令调整到合适的地址边界。


关键点:

重新排列结构的做法可能不行。一些结构可能会映射到一个数据包的定义。

多处理器访问相同高速缓存行将会产生额外的处理器间通信。

如果可能,线程间共享的数据结构应该拆分开,不同的线程使用独立的数据结构。


10. 高速中断服务程序

上下文:您的应用程序使用多个中断接口上的数据并触发数据的处理。

问题:中断服务例程可以与其他ISR和实时线程协同工作,实时线程如,网络包处理的内核线程。


解决方法:保持的ISR简短。并且设计成可重入的。

例如,ISR可以释放一个信号,设置一个标志,推一个数据包进队列。您应该在ISR之外,读取队列和处理数据。这样,您避免了在ISR等待数据的锁。

频繁的ISR处理锁会使整个系统产生不可预知的结果。


关键点:

在中断服务程序错误通常有一个灾难性的影响。他们保持简单可减少错误的概率。


11. 汇编语言

背景信息:已经鉴定了数据路径中消耗最显著的C函数。

问题:为此函数生成的执行代码并不是最优的,或者对您的处理器而言还可进一步优化。


解决办法:用汇编语言直接重新实现关键功能。

使用最好的编译器(见模式-应用最佳的编译器)来生成初始汇编代码,然后手工优化。


关键点:

对于复杂的处理器,现代编译器技术已经开始在优化考量上优于人类。

汇编语言是阅读和维护更加困难。

汇编语言移植到其他的处理器更加困难。


12. 内联函数

上下文:你已经确定了一个小的C函数被频繁调用,尤其在数据通路上的C函数。

问题:应用程序数据路径频频调用的小函数,进入和退出函数带来的开销会变得很显著。


解决方法:声明函数内联。这样,函数被直接插入到调用函数的代码中。


关键点:

内联函数会增加应用程序的代码大小,并给代码高速缓冲带来压力。

有些调试器显示不能调试内联函数。



13. 优化循环

背景信息:您已经确定了某个循环,它是消耗数据通路性能的显著部分。

问题:循环的结构或在其上进行的操作数据可以“捣毁”数据缓存。


解决方法:可以考虑一些循环/数据优化:

数组合并--- 如果循环使用了两个或多个数组,把它们合并到一个数组结构中,减少高速缓存的更新。

感应变量交换。感应变量指的是每次迭代定额增加或减少的变量。(比如循环中的i

循环融合


关键点:

循环优化可能使代码难以阅读,理解和维护


14. 减少局部变量

上下文:你已经确定了需要优化的函数。它含有大量的本地变量。

问题:大量的局部变量可能会增加它们在堆栈上的开销。编译器需要生成代码来设置和恢复帧指针。


解决方案:减少局部变量的数目。使得编译器可以存储所有的本地变量和参数到处理器寄存器中。


关键点:

删除局部变量会降低代码的可读性,或者需要执行额外计算。


15. 显式使用寄存器

上下文:你已经确定了需要优化的函数。一个局部变量或数据块频繁被使用。

问题:有时,编译器无法识别一个变量可以进行寄存器优化。


解决方法:显式指定寄存器优化局部变量,这是经常使用的方法。

另外比如,数据包的包头在算法路径上被频繁使用,可以进行寄存器优化。在一个实际的应用,这种类型的优化对性能改进大约20%。

或者,你可以用一个局部寄存器变量,缓存经常使用的全局变量。比如,你知道这个全局变量不被任何ISR修改,并且在某个函数中被频繁使用,比如,在一个处理多个包的循环中的统计变量,您可以先把这个全局变量赋值到局部寄存器变量中,然后在退出函数之前,更新这个全局变量。


关键点:

寄存器(register)关键字对编译器只是一个提示。


16. 优化硬件寄存器操作

上下文:算法路径上,对一个或多个硬件寄存器进行多次读和写操作

问题:对硬件寄存器的”读-操作-写“可导致处理器停顿。


解决方案:首先,分解”读-操作-写“语句来隐藏部分操作硬件寄存器存在的延时

例如:

-操作-写“硬件寄存器:

* reg1ptr |=0x0400;

* reg2ptr &=0x80;

优化如下:

reg1 = * reg1ptr;

reg2 = * reg2ptr;

reg1 |= 0x0400;

reg2 &= ~0x80;

* reg1ptr = reg1;

* reg2ptr = reg2;

此修改后的代码消除了读相关延迟之一。

第二,合并数据路径中多个写入相同硬件寄存器中的代码。例如,某些应用程序禁用

硬件中断时,多次设置/复位中断使能寄存器。在一个这样的应用中,当我们手动合并这些写指令,性能改良约4%。


关键点:

分离”读-操作-写“代码,它也将增加本地变量,进而可能引发帧指针的创建。


17. 避免使用OS缓冲池

上下文:应用程序使用了系统缓冲池。

问题:在一些操作系统,系统缓冲池的管理使用了,锁定中断和使用本地信号来保证同步。这些都非常耗时。设计阶段,要特别注意缓冲管理。尤其是否在数据路径上用到这些OS管理的缓冲池。


解决方案:避免在数据路径上用到或操作OS管理的数据缓冲池。在数据路径之外,预先分配好包缓冲池,并将它们存储在轻量级软件池/队列中。

堆栈或数组在管理缓冲区时通常比链表更有效,因为它们需要较少的内存访问来添加和删除缓冲区。栈还提高了数据高速缓存区的利用率。


关键点:

•OS缓冲池实现缓冲区的管理。另写一个功能上重复了。



18. C语言的优化

背景信息:您已确定的函数或代码段正显著消耗着数据路径上的CPU时钟周期。你可能是使用性能分析工具(见模式-分析工具)定位到代码。

问题:C代码的函数或段需要优化。


解决方法:可以尝试一些C语言级优化:

使用引用传递大的函数的参数,而不是参数值。参数值需要时间来复制。

避免数组索引。使用指针。

减少循环的嵌套,合并到单一的循环中。

避免长的IF-THEN-ELSE链。使用switch语句或状态机。

使用int(处理器的天然字长)来存储变量,而不是charshort

尽可能使用unsigned变量和参数。这样做可能会带来一些编译器优化。

避免数据路径上的浮点计算。

使用递减循环变量,例如,

for(i = 10; i --;{dosomething}

甚至更好

do { something )while (i --);

读读你的编译器生成的这段汇编码。

调整结构体,大小为2的幂。

•if-else语句中,把最可能出现的语句放在if段里

使用return返回值,而不是某个指针参数,许多处理器返回值约定存储在一个寄存器中。


关键点:

良好的编译器做了部分这些优化。


19. 关闭计数器/统计

背景:许多应用程序有大量与数据路径相关联的统计/计数器。您已完成应用程序的集成测试。

问题:软件保留了一些计数器和统计,以便系统整合和调试。这些计数器,通常需要读取和

写,主内存,或高速缓存。


解决方法:找找是否有宏可以禁用这些计数器和统计,还包括一些仅在集成调试阶段用的检查代码。

在一种应用中,使用该模式高达4%的吞吐量提升。


关键点:

禁用计数器和统计也失去了有用的调试信息。

0 0
原创粉丝点击