Blackfin C语言优化
时间:02-28
来源:互联网
点击:
在这张图里我们看到的是一根程序性能随C语言和汇编语言在程序中比例变化而变化的曲线。整条性能曲线开始在A点,可以把它叫做out-of-boxA点,就是程序员或者用户用自己未经优化的C语言程序在DSP上编译和运行能够达到的性能。这个性能取决于程序的复杂度和编译器的性能,但通常不会很高,大约在30%左右。这未经优化的C语言我们把它叫做out-of-boxC。这里的30%是什么意思呢,就是你有一个600MHz的DSP去运行out-of-boxC的程序,内核被占满了,但能做的有用的事情(性能)只相当于一个180MHz的DSP。为什么会这样呢,前面已经提到了,那就是out-of-boxC不是为DSP量身定做的,不能充分用到DSP的各项性能。好,从A点开始向B点运动,我们就进入了今天要讨论的范围,也就是进入了“Box”。这个时候我们在out-of-boxC的基础上,在C语言的范围内面向我们要使用的DSP,对程序经行优化,就来到了B点。在B点,能够达到的性能是大约70%~80%。要注意,从A点到B点,所有的工作都是在C语言的范围内进行的,并没有进入汇编语言的范畴。这个时候的C语言可以叫它做OptimizedC,它是在out-of-boxC的基础上加入针对当前DSP的扩展而形成的。如果沿着性能曲线进一步向前,就进入了汇编语言的范畴,也就是程序员开始把一部分重要的,大量消耗cycle的程序改写为汇编。随着被改写的程序的增多和进入汇编领域的深入,我们达到了整条性能曲线的顶点——C点。这时大致有20%左右的代码已经被汇编语言代替,而程序的性能也已经超过了90%,也就是我们基本上充分利用了整颗DSP的全部性能。在C点的位置,程序是处在一个混合编程的状态——OptimizedC和汇编的混合编程。这里可以使用C语言可调用的汇编子程序以提高重用性和可维护性。有读者可能好奇了,如果进一步扩大汇编语言在程序中的比例是不是可以继续提高性能呢。实际的情况跟我们想象的并不完全相同,性能不升反降了。举一个极端的例子,如果把所有的C语言都用汇编改写,我们就处在整条性能曲线的D点。在这里,程序的整体性能并没有C点高。这主要是因为C语言作为一种高级语言在控制、跳转代码,以及对复杂数据结构的访问上相对汇编语言有很大优势。
对整条性能曲线可以做这样的总结,1)最佳性能产生在C和汇编按一定比例分配的情况下,80-20可以作为一个参考;2)将所有代码都转为汇编并不会带来性能的进一步提高;3)在C语言编译器的帮助下,将大多数控制代码保留在C语言范畴中是可能的;4)要想达到最佳性能,那些消耗cycle最多的代码应转化为C语言可以调用的汇编函数。简单说就是让C和汇编语言做各自擅长的事情,在动态平衡中达到最佳性能。内事不决问张昭,外事不决问周瑜,各司其职。
在DSP性能大幅提高的今天,如果可以如图中B点那样用Optimized C将C语言在DSP上的性能提高到%70以上,很有可能对于大多数应用场景就已经足够了,并不是一定要接触汇编语言的。这个从A点到B点的过程也正是这篇文章要讨论的重点。
2. 是骡子是马您先别溜
说到这里,有很多朋友等不及要开始做优化了:打开程序,一条语句、一条语句立刻看起来。很多时候我们在工作中都遇到这样的情况,所以第一刻就要喊停,等我先讲讲一些容易被忽略的东西。
首先最容易被忽略的是数据类型。通常编译器对ANSIC所有数据类型都是支持的,但是硬件呢,是不是对所有的数据类型都很有效的支持呢?举个例子,很多DSP都有专门针对16-bit定点运算的指令,特别是一些并行指令。如果在算法中可以将数据类型设计为16-bit就可以充分利用到这些指令。Blackfin每个cycle可以做2个16-bit乘法,而每个32-bit乘法则要消耗3个cycle。这中间有6倍的差距,是值得我们考虑的。另外定点芯片不直接支持浮点操作,如果算法中有浮点类型和浮点运算,则首先应该考虑在不影响动态范围和精度的基础上进行定点化。因为在定点芯片上每个浮点操作都可能消耗成百上千个cycle来得到近似的结果。对于小数类型,Blackfin直接支持1.15和1.31小数类型的操作,这给程序员很大的灵活度。所以我们首先要尽可能依托当前DSP最擅长的操作来确认数据类型被支持的程度,并对算法进行调整。
另一个容易被忽略的地方是算法本身。也就是被采用的算法本身是不是已经是最高效,最优的。考虑一下正在用的排序算法是不是还有余地改进;要用的正弦波形是计算还是查表;又或者整个算法或者部分可以被更高效的算法代替。这样的考虑往往可以达到事半功倍的效果,就好像换了三趟公交去看朋友,下车一抬头发现有条地铁直达。
在现代高性能DSP中通常都有比较深的指令流水线。流水线的作用是把一个cycle里要做的事情分在多个步骤里来做。对于高主频的芯片而言,流水线的深度是很重要的,它从某种程度上决定了可能的最高主频速度。每一个节拍,指令流水线上不同功能单元同时并行运作,每条指令按顺序流经这些功能单元。可惜事物总有两面性,当流水线遇到了条件跳转,它的另外一面就充分暴露出来了。那就是在跳转的时候,当前指令之后已经在流水线里的指令全部都要被清空,然后再让要跳转到的目的指令重新进入流水线。如果流水线的深度是N,那么这里损失的cycle通常为N-1。流水线越深,损失越大。如果不巧这个条件跳转在循环里面,这个N-1的损失就会被放大了。用一些方式替代条件跳转可以减轻这样的损失,比方说尽可能的使用条件执行和条件赋值,或者max和min语句,因为这些语句的执行通常可以在DSP的汇编级找到对应的单周期语句。另外就是要尽可能的避免在循环中使用条件跳转。
除法运算是我们需要注意的一种操作,因为通常除法在DSP中都是一段近似算法来实现的。比如说在Blackfin提供两种除法近似,精度较低的一种需要大约40 cycle而32bit除法则需要大致400cycle。想想一个1000次的for循环里如果有3次除法,您就大致知道您的程序会跑多慢了。所以我们要在算法中考虑到除法的影响和可能的替代方式,例如利用不等式原则可以把除法变成乘法,又或者模2的除法可以变成移位。当然了,我在这里提到的替代,包括针对前面的数据类型,算法和条件跳转,都是遵循“尽可能”的原则,没有绝对的意思。优化的后程序效率的高低就是体现在这个尽可能上。
对整条性能曲线可以做这样的总结,1)最佳性能产生在C和汇编按一定比例分配的情况下,80-20可以作为一个参考;2)将所有代码都转为汇编并不会带来性能的进一步提高;3)在C语言编译器的帮助下,将大多数控制代码保留在C语言范畴中是可能的;4)要想达到最佳性能,那些消耗cycle最多的代码应转化为C语言可以调用的汇编函数。简单说就是让C和汇编语言做各自擅长的事情,在动态平衡中达到最佳性能。内事不决问张昭,外事不决问周瑜,各司其职。
在DSP性能大幅提高的今天,如果可以如图中B点那样用Optimized C将C语言在DSP上的性能提高到%70以上,很有可能对于大多数应用场景就已经足够了,并不是一定要接触汇编语言的。这个从A点到B点的过程也正是这篇文章要讨论的重点。
2. 是骡子是马您先别溜
说到这里,有很多朋友等不及要开始做优化了:打开程序,一条语句、一条语句立刻看起来。很多时候我们在工作中都遇到这样的情况,所以第一刻就要喊停,等我先讲讲一些容易被忽略的东西。
首先最容易被忽略的是数据类型。通常编译器对ANSIC所有数据类型都是支持的,但是硬件呢,是不是对所有的数据类型都很有效的支持呢?举个例子,很多DSP都有专门针对16-bit定点运算的指令,特别是一些并行指令。如果在算法中可以将数据类型设计为16-bit就可以充分利用到这些指令。Blackfin每个cycle可以做2个16-bit乘法,而每个32-bit乘法则要消耗3个cycle。这中间有6倍的差距,是值得我们考虑的。另外定点芯片不直接支持浮点操作,如果算法中有浮点类型和浮点运算,则首先应该考虑在不影响动态范围和精度的基础上进行定点化。因为在定点芯片上每个浮点操作都可能消耗成百上千个cycle来得到近似的结果。对于小数类型,Blackfin直接支持1.15和1.31小数类型的操作,这给程序员很大的灵活度。所以我们首先要尽可能依托当前DSP最擅长的操作来确认数据类型被支持的程度,并对算法进行调整。
另一个容易被忽略的地方是算法本身。也就是被采用的算法本身是不是已经是最高效,最优的。考虑一下正在用的排序算法是不是还有余地改进;要用的正弦波形是计算还是查表;又或者整个算法或者部分可以被更高效的算法代替。这样的考虑往往可以达到事半功倍的效果,就好像换了三趟公交去看朋友,下车一抬头发现有条地铁直达。
在现代高性能DSP中通常都有比较深的指令流水线。流水线的作用是把一个cycle里要做的事情分在多个步骤里来做。对于高主频的芯片而言,流水线的深度是很重要的,它从某种程度上决定了可能的最高主频速度。每一个节拍,指令流水线上不同功能单元同时并行运作,每条指令按顺序流经这些功能单元。可惜事物总有两面性,当流水线遇到了条件跳转,它的另外一面就充分暴露出来了。那就是在跳转的时候,当前指令之后已经在流水线里的指令全部都要被清空,然后再让要跳转到的目的指令重新进入流水线。如果流水线的深度是N,那么这里损失的cycle通常为N-1。流水线越深,损失越大。如果不巧这个条件跳转在循环里面,这个N-1的损失就会被放大了。用一些方式替代条件跳转可以减轻这样的损失,比方说尽可能的使用条件执行和条件赋值,或者max和min语句,因为这些语句的执行通常可以在DSP的汇编级找到对应的单周期语句。另外就是要尽可能的避免在循环中使用条件跳转。
除法运算是我们需要注意的一种操作,因为通常除法在DSP中都是一段近似算法来实现的。比如说在Blackfin提供两种除法近似,精度较低的一种需要大约40 cycle而32bit除法则需要大致400cycle。想想一个1000次的for循环里如果有3次除法,您就大致知道您的程序会跑多慢了。所以我们要在算法中考虑到除法的影响和可能的替代方式,例如利用不等式原则可以把除法变成乘法,又或者模2的除法可以变成移位。当然了,我在这里提到的替代,包括针对前面的数据类型,算法和条件跳转,都是遵循“尽可能”的原则,没有绝对的意思。优化的后程序效率的高低就是体现在这个尽可能上。
- ADSP-TSl01S嵌入式系统的混合编程(05-18)
- 单片机C语言基础编程源码六则(10-28)
- TMS320C6000嵌入式系统优化编程的研究(04-08)
- 基于Proteus和ADS的ARM虚拟实验室建设(05-12)
- ARM入门最好的文章(转)(05-20)
- 如何将一个开源游戏移植给一款32位微控制器(05-26)
灏勯涓撲笟鍩硅鏁欑▼鎺ㄨ崘
栏目分类