DSP

DSP C语言优化-(二)

2019-07-13 17:24发布

工作流程一般分为三个阶段。
 阶段一:直接按照需要用C语言实现功能。在实际的DSP应用中,许多算法都是非常复杂,直接用汇编代码编写,虽然优化效率很高,可是实现的难度却很大,所以一般都采用先用C语言来实现,然后编译运行,利用C64X开发环境的profile?clock工具测试程序运行时间,若不能满足要求,则进行第二阶段。
 阶段二:C语言级的优化。选择C64X开发环境提供的优化方式以及充分运用其他技巧,优化C代码,若还不能满足效率要求,则进行第三步。
 阶段三:汇编级的优化。将上一阶段C程序中优化效率较低的部分提出来,用线性汇编语言编写,利用汇编优化器进行优化。汇编优化器的作用是让开发人员在不考虑C64X流水线结构和分配其内部寄存器的情况下,编写线形汇编语言程序,然后汇编优化器通过分配寄存器和循环优化将汇编语言程序转化为利用流水线方式的高速并行汇编程序。 上述的三个阶段不是都必须经过,当在某一阶段获得了期望的性能,就不必进行下一阶段的优化。   1) 选用C编译器提供的优化选项
 在编译器中提供了分为若干等级和种类的自动优化选项,如下:
 ● -o:使能软件流水和其他优化方法
 ● -pm:使能程序级优化
 ● -mt:使能编译器假设程序中没有数据存储混淆,可进一步优化代码。
 ● -mg:使能分析(profile)优化代码
 ● -ms:确保不产生冗余循环,从而减小代码尺寸
 ● -mh:允许投机执行
 ● -mx:使能软件流水循环重试,基于循环次数对循环试用多个方案,以便选择最佳方案。
 根据实际编译的程序,选择合适的优化选项,进行源程序的优化。   2) 减小存储器相关性
 为使指令达到最大效率,C64X编译器尽可能将指令安排为并行执行。为使指令并行操作,编译器必须知道指令间的关系,因为只有不相关的指令才可以并行执行。当编译器不能确定两条指令是否相关时,则编译器假定它们是相关的,从而不能并行执行。设计中常采用关键字const来指定目标,const表示一个变量或一个变量的存储单元保持不变。因此,在代码中加入关键字const,可以去除指令间的相关性。例如下面的程序:
 void vecsum(short *sum,short*in1,short*in2,unsigned int N)
 {
 int i;
 for(i=0;i  sum[i]=in1[i]+in2[i];
 }
 由其相关图2(a)可见,写sum可能对指针in1、in2所指向的地址有影响,从而in1和in2的读操作必须等到写sum操作完成之后才能进行,降低了流水效率,为帮助编译器确定存储器的相关性,使用const关键字来指定一个目标,上面的源程序可改为含关键字const的优化源代码:
 void vecsum(short * sum, const short*in1,const short*in2,unsigned int N)
 {
 int i;
 for(i=0;i  sum[i]=in1[i]+in2[i];
 }  由其相关图2(b)可见,由于使用了关键字const,消除了指令之间的相关路径,从而使编译器能够判别内存操作之间的相关性,找到更好的指令执行方案。 3) 使用内联函数(intrinsics)
 内联函数是C64X编译器提供的专门函数,它们与嵌入式的汇编指令是一一对应的,其目的是快速优化C源程序。在源程序中调用内联函数,与调用一般的函数相同,只不过内联函数名称前有下划线作特殊标识。当汇编指令功能不易采用C语言表达时,可采用内联函数表示。例如在定点运算中经常要求出源操作数的冗余符号位数,这一功能如果用C完成的话,需要如下的代码:
 unsigned int norm(int src1)
 {
 unsigned int sign, result = 0;
 sign = src1 & 0×80000000;
 while(1)
 {
 if(sign)
 {
 if((src1 = src1 << 1) & sign)
 result += 1;
 else
 return result;
 }
 else
 {
 if((src1 = src1 << 1) | sign)
 return result;
 else
 result += 1;
 }
 }
 }
该源程序代码冗长,有较多的逻辑操作和判断跳转,运行效率低下。若用内联函数,则是result =_norm(src1),减少了代码长度,提高了运行效率。因此对于需要大量C代码才能表示的复杂功能,应该尽量用C64X的内联函数来表示。   4) short型数据的int处理
C64XDSP具有双16bit扩充功能,芯片能在一个周期内完成双16bit的乘法、加减法、比较、移位等操作。在设计时,当对连续的short型数据流操作时,应该转化成对int型数据流的操作,这样一次可以把两个16位的数据读入一个32位的寄存器,然后用内部函数来对它们处理(如_sub2等),充分运用双16bit扩充功能,一次可以进行两个16bit数据的运算,速度将提升一倍。 5) 尽量少进行函数调用
函数调用的时候,要将PC和一些寄存器压栈保存,函数返回时,则将这些寄存器出栈返回,增加了一些不必要的操作。所以一些小的函数,最好是用适当的内联函数代替直接写入主函数里,一些调用不多的函数,也可以直接写入主函数内,这样可减少不必要的操作,提高速度。但是这样往往会增加程序的长度,因此是一种利用空间换取时间的办法。 6) 尽量使用逻辑运算代替乘除运算 在DSP里,乘除运算指令的执行时间要远远超过逻辑移位指令,尤其是除法指令,在设计的时候,可以根据实际情况,进行一些调整,尽量用逻辑移位运算来代替乘除运算,这样可以加快指令的运行时间。 7) 软件流水线技术的使用
软件流水线技术用来对一个循环结构的指令进行调度安排,使之成为多重迭代循环并行执行。在编译代码时,可以选择编译器的-o2或-o3选项,则编译器将根据程序尽可能地安排软件流水线。 在DSP算法中存在大量的循环操作,因此充分地运用软件流水线方式,能极大地提高程序的运行速度。但使用软件流水线还有下面几点限制:
● 循环结构不能包含代码调用,但可以包含内联函数。
● 循环计数器应该是递减的。
● 循环结构不能包含break,if语句不能嵌套,条件代码应当尽量的简单。
● 循环结构中不要包含改变循环计数器的代码。
● 循环体代码不能过长,因为寄存器(32个)的数量有限,应该分解为多个循环。
在软件流水线的运用上,应该尽量使复杂的循环分解成简单的小循环,以避免寄存器的数量不够;对于过于简单的循环,应该适当的展开,以增加代码数量,增加流水线中的迭代指令。 8) 采用指令乱序技术
 程序中,有些指令的执行顺序没有严格的要求,可以作出一些位置上的调整,因此可以适当的调整这些指令的位置,穿插于其他的指令之中,从而减小指令的相关性,增加运行时的并行性。
尤其在循环里,当循环体较小的时候,可以把多个循环的代码写在一个循环体里,合并成一个循环,从而减小循环内指令的相关性,增加指令运行的并行性。但是要注意不要使循环过于复杂,以至不能进行软件流水线的优化。   由于C语言编译出来的程序,不是最有效率的汇编语言,而没有办法达到实时播放。所以为了要使程序执行的速度能够加快,必须要做最佳化,使其能够达到实时播放的速度。然而C6x 的编译器也提供了最佳化的指令,如在编译时加上 -o3 的参数,它可以用软件来分析我们的程序是否有可以改进的地方,如此一来,在产生组语的汇编语言档案之前,编译器会对我们写的C语言程序不断的进行编译,也会对程序中的循环部份重新编排,产生另一较有效率的核心循环,以最有效率的方式重新编排程序,来加快程序速度