xih

在编写和调试C6000程序时,为了使C6000代码获得最好的性能,我们需要按照软件编程的3个阶段进行,每个阶段完成的任务如下[4]:

第一阶段:开始可以不考虑C6000的有关知识,完全根据任务编写C语言程序。在CCS环境下用C6000的代码产生工具,编译产生在C6000内运行的代码,证明其功能正确。然后再用CCS的调试工具,如debug和profiler等,分析确定代码可能存在的、影响性能的低效率段。为进一步改进代码性能,需要进入第二阶段。

第二阶段:利用内联函数、CCS编译选项和其他具体优化方法改进C语言程序。重复第一阶段,检查所产生的C6000代码性能。如果产生的代码仍不能达到所期望的性能,则进入第三阶段。

第三阶段:从C语言程序中抽出对性能影响很大的程序段,用线性汇编重新编写,再用汇编优化器优化,链接,直到达到所期望的性能要求。

具体到G.729A标准编解码器的实时要求,第三阶段是工作的重点,而且线性汇编的重新编写要求对程序代码和DSP的特性有充分的了解。

3. G.729A代码的剖析

CCS集成开发环境为软件开发人员提供了高效的开发、调试工具。特别是它提供了评价器( profiler)的优化工具,通过收集在指定代码区间程序执行的统计性能,分析确定程序中各个段、各个子函数所花费的处理器时间,从而把程序的优化集中在对程序性能影响最大的代码段上去[5]。其两种不同的测试方法是:

(1) 在需要测定复杂度的程序段的开头和结尾处设定两个断点,打开时钟窗口,运行程序。在第一个断点处执行停止,这时双击时钟窗口使之清0,接着继续执行程序,在第二个断点处停止,这时,时钟窗口显示的值便是该段代码的复杂度。这在测试程序中一个函数的复杂度是非常有用的。

(2) 先打开统计窗口,在需要测试的程序段头尾设置统计点((Probe Point)。程序运行结束后,统计窗口内该程序段后面的统计值便是该代码段的复杂度。这种方法较简单,统计点自动收集统计信息,无需手工干涉,这在测定程序多段代码的复杂度是非常有用。

4. 线性汇编的优化

线性汇编是TI提供的一种汇编语言,其指令系统和汇编语言的指令系统完全相同,但在编写时不需要指定寄存器和操作单元,也不需要考虑延时的问题,因此编写线性汇编相对要容易一些 [6]。

经过第一阶段和第二阶段的优化后,音频编码程序在DM642上的运行状况有了很大改善,但是经测试仍然没有到达实时效果,而高级语言的效率几乎发挥到了极致,测试的速度达到了36.5帧/s,是未优化之前的10倍。这时,我们采用线性汇编语言重新编写C代码的低效率段程序,进一步提高程序的执行效率和充分利用DM642的硬件资源,最终按设计要求在DM642实时实现G.729A编码。在前面的DSP开发流程已经提过,DSP开发的最后一个手段是用汇编重写C代码,它是唯一可以既提高程序执行速度又可以减少程序体积的方法。由于针对并行处理器编写汇编的难度很大,一般采取的是混合编程的方法,即程序的主要部分用C代码,部分耗时较大的函数可以用线性汇编改写。

在编写线性汇编优化代码的过程中,为了提高代码执行效率,我们需要遵循以下原则[7]:

(1)写并行代码:通过使用汇编指令并行执行的方法减少循环内的执行周期数,优化线性汇编代码。这里的关键问题是弄清指令相关性,只有不相关的指令才能并行执行。辨别指令是否相关,可以使用相关图。

(2)处理跳转指令和转移指令:汇编程序的一大特点就是频繁地跳转,当满足不同的条件时,要求程序进行不同的操作,或跳到相应的位置。对于“大于”、“大于等于”、“小于”、“小于等于”等较为接近的逻辑判断和处理,应慎重对待,否则将产生逻辑性错误,并且很难调试。当发生溢出需进行相应处理时,这种现象尤为突出。

(3)尽量减少循环体内的指令数:G.729A的算法实现,有许多是在循环内部完成的,有些地方如固定码本搜索过程中,为了确定四个非0脉冲的位置和幅度,还用到了多重循环。在循环内部,特别是在嵌套较深的循环内部,减少一条指令可以大大降低程序的操作次数。例如,对于一个每重循环8次的四重嵌套循环,在最内层循环每减少一条指令,整个程序可以少执行84=4096语句。因此在设计程序时,能够放在循环体外执行的语句,尽量放在循环体外执行。

(4)展开程序体:在一定条件下,尽量展开程序,以减少子程序的调用和返回次数,牺牲空间换取时间。

G.729A算法中的LPC模块、LSP量化及激励码本搜索耗时最多,为进一步提高代码效率,对相关计算、FIR滤波等部分函数用线性汇编语言进行了改写,并用画相关图等方法有针对性的进行优化。经汇编优化器优化后,代码效率比C语言直接编译有明显提高。

5. 优化工作的创新点

在对G.729A的优化中,本文在前人研究成果的基础上,针对TMS320DM642 DSP系列芯片提出了一些有价值的新方法。这些创新点在不同程度上提高了代码的优化速度和执行效率,在语音编解码的DSP实时实现中起到了关键性作用。下面,以举例的方式阐明一些经典的方法。

5.1 绘制分析图,掌握函数结构

对于一个语句较多、结构复杂的函数,为了充分了解其逻辑结构和语句的相关性,我们通常采用画分析图的方法。分析图的形式比较灵活,可以根据具体的情况选用不同的制图工具。在编写线性汇编的时候,需要考虑存取数组中的元素,数据打包操作和数据相关性等问题,分析图有助于正确处理这些问题。

在对函数Cor_h_X( )优化过程中,我们遇到了一定的困难,原因在于其中有一个双层的循环体,内层的次数与外层有关,外层的循环次数为40,并且循环内部的语句有先后的相关性。这样的结构如果用循环展开的方法将会用到大量的寄存器,数目超出了64个,需要开辟额外的内存空间去存放临时变量,而读写内存会消耗较多的时间,因此这样执行效率不会有充分的提高。对此,我们利用分析图描述了函数中关键代码的数组X[ ],h[ ]的使用情况,如图1所示:

图1 cor_h_X( )函数分析图(部分)

图1直观地反映了数组16位h[ ]和16位X[ ]之间的乘加关系,从函数cor_h_X( )中可知,两个数组的乘积之和要对应的保存在临时数组32位Y[ ]中。通过研究此分析图,我们发现h[ ]与X[ ]中的一些元素进行乘积和处理之后就不再被使用,那么存储这些元素的寄存器可以存放中间结果(Y[]的元素),这样就可节省寄存器的使用个数,免去了开辟内存空间和中间变量的存取指令。

对于函数cor_h_X( ),利用上述思想编写线性汇编,只需要定义57个寄存器就可以完成所用的操作,存取指令从1760条优化到30条,仅为原来的1/60。同时执行速度从390072个时钟减少到35871个,降为原来的1/10。

绘制的分析图可以包含相关图,相关表等,使资源安排更加合理。该方法在其他函数的改写中也多次使用到。

5.2 功能相似的函数或代码段合并为一个函数

线性汇编在提高代码效率的同时也成倍的增加了代码尺寸,以上述cor_h_X( )为例,它在该写后代码尺寸从660条增大到7776条(该数据由CCS剖析工具分析所得)。在工程应用中,对于有限的内存程序区,我们会适当减少程序占用的空间。合并功能相似的函数可以达到这一要求。

在LSP量化处理中,源代码中给出了2个LSP选择函数:Lsp_select_1( )和Lsp_select_2( ),而我们发现它们具有相同的功能和相似的结构,因此,在对两者的线性汇编改写中,我们只需编写一个函数(命名为Lsp_select)即可实现LSP量化处理中这两个模块的功能。

另外,在对于一些数组拷贝,数组初始化的代码,我们同样可以用此方法,编写一个函数实现,这样可以在提高执行效率的同时,减少程序占用的内存空间。

5.3 多个循环合并为一个循环

C代码改写线性汇编的时候,我们常常会发现,只要作一些调整,两个或多个循环完成的操作完全可以由一个循环来完成。以LPC子模块240点加窗语音的自相关计算Autocorr()函数为例,经过优化改写的C代码(部分)如下:

for(i=0; i<L_WINDOW; i++) //第一个循环体

y[i] = (_smpy(x[i], hamwindow[i])+0x00008000L)>>16;

sum = 1; //避免为0的情况

for(i=0; i<L_WINDOW; i++) //第二个循环体

sum = _sadd(sum,_smpy(y[i], y[i]));

这段代码包含了两个for循环,在CCS中直接编译运行并行度很差,利用线性汇编重写代码。我们发现两个循环体的循环次数均为60(L_WINDOW=60),所处理的数组不同,并且两个循环没有相关性,可以把第一和第二个循环合并成一个循环。前者的功能是对语音信号进行加窗;后者是实现乘累加(Mac)。两者合并后采用线性汇编编写,其代码如下:

mvk 60,i //设置循环次数

loop1: lddw *ham++,hamih:hamil //hamwindow[]指针

lddw *x++,xih:xil //x[]指针

smpy2 hamil,xil,yi1:yi0 //两对16位操作数相承,并行执行

smpy2 hamih,xih,yi3:yi2

sadd yi0,con0x8000,yi0

sadd yi1,con0x8000,yi1

sadd yi2,con0x8000,yi2

sadd yi3,con0x8000,yi3

packh2 yi1,yi0,yl //数据打包技术

packh2 yi3,yi2,yh

stdw yh:yl,*y++ //双字存取,提高执行效率

smpy2 yl,yl,yi1:yi0

sadd sum0,yi1,sum0

sadd sum0,yi0,sum0

smpy2 yh,yh,yi3:yi2

sadd sum0,yi3,sum0

sadd sum0,yi2,sum0

add i,-1,i

[i] b loop1 //把第一和第二个循环合成一个大循环,减少转移次数

产生的汇编代码并行流水性能大大增加,耗费的时钟周期数从1310000减少到15000,少于改编前的1/8。

6. 结束语

关于编解码器执行的时钟周期,在线性汇编改写前后,文件版本通过CCS的profiler剖析工具得知:每10帧(100MS)从159700000降至68500000,仅为原来的42%。硬件版本进行测试得:编解码的帧数提高到了88帧/s以上,鉴于编码、解码的时间比例为5:1,所以,本系统编码已经达到100帧/s,完全符合实时通信的要求。