1.前言
DSP软件优化是DSP软件开发人员经常面临的课题。虽然TI的集成开发环境CCS提供了丰富的优化手段,但在产品开发阶段,考虑的比较多的往往是代码的可读性。正确性以及调测的方便,而函数执行效率方面不一定能全面照顾到,从而在后期面临代码优化工作。
在一个复杂的DSP系统上运行的软件工程函数众多,软件优化除了文件级优化之外,往往需要对某些重点函数进行优化。而如何从大量的函数中准确的提取出执行效率低。占用CPU资源多的重点优化函数,成为函数级优化之前的首先要解决的问题。以往测试函数执行时间,一般在函数前后添加记录时间点的测试代码,在Emulator环境或真实环境中跑一下测得函数执行时间,或者在Simulator环境下使用CCS提供的Profile功能,测得函数执行占用的cycle数。无论哪种方法,都只能逐个函数进行,效率低下,而且不能直接得出函数执行频率这一优化需要参考的重要指标(因为有些函数虽然执行消耗cycle很多,但其执行次数少,也不是占用CPU的主因)。如果对各函数的执行频率进行理论上的计算,再结合打点测试结果做通盘的比较,做起来也是效率低,而且面临与实际可能有较大出入的问题。
2.函数执行时间测量
利用TI CCS3.3(6.1.12版本以上)集成开发环境自带的函数进出钩子(Function entry/exit hooks)的新功能,可以方便的实现整个软件工程运行过程中执行到的全部函数(库函数及inline函数除外)进入和退出时间点的记录,再配合相关的脚本分析工具,很快一张所有运行到的函数执的行时间和执行次数的汇总表,便呈现在眼前。
2.1 编写函数进出钩子(hook)函数,构造测试工程
钩子函数的功能,是在函数进入和退出时进行调用,记录下函数指针和进出的时间点,保存在一片专门开辟的内存空间。这片空间在这里我们称之为“hook信息记录区”.hook信息记录区大小可以自己根据需要制定。如果占用内存空间往往比较大,可存放在片外DDR2内存空间。在hook信息记录方式上,由于后续配套分析脚本的需要,采用顺序记录(而不是循环记录)的方式。另外由于钩子函数调用极其频繁,编写时需要注意提高hook函数的执行效率。Hook函数主要包括初始化函数。进入函数时调用的hook函数以及退出函数时调用的hook函数。下面是一个进入函数时调用的hook函数代码(其中gaqRecord[]数组空间就是hook信息记录区);退出函数时调用的hook函数,可以跟FuncHookEnter相同,也可以不同,相同时可以直接用FuncHookEnter.
本例中,将FuncHook.c文件加入到DSP软件工程中,并使用-o3优化(提高执行效率)。并修改DSP工程的编译选项,登记钩子进出函数,具体设置方法为,打开Build Options对话框,选中C o m p i l e r – > C a t e g o r y ->Advance(2),然后在“Entry hook function”一栏中填入自己定义的进入函数的钩子函数名称,例如此处填入FunHookEnter,“Caller's parameter topass”从下拉框中选择“address”,退出函数的hook函数的设置一样。
由于hook信息记录区大小有限,而且采用顺序记录的方式,hook信息记录区很快就记录满了,之后便停止记录,因此,在记录时可以通过对索引变量进行了判断,该索引初始化为不记录,在做好一切准备工作(如DSP加载后,把系统功能都运行起来)之后,通过一个调试开关,在idle任务执行函数中检测到该开关变为CTRUE后,将索引置为0,开始记录hook信息。另外,该idle任务函数执行很频繁,实际测试一般不需要记录该函数的进出时间信息,所以可以使用#pragma NO_HOOKS将该函数屏蔽掉。
2.2 脚本工具自动化分析记录的数据
编译测试工程,生成DSP版本并加载运行,做好准备工作(如运行特定功能)之后,通过调试命令的方法将gudRecordIdx索引置0,开始触发记录hook信息。记录区满后(可以通过读取gudRecordIdx变量值来确认),将hook信息记录区(即:gaqRecord[]数组)读出,传送到后台PC机保存生成一个dat文件。
由于hook信息记录是记录区写满后即停止,所以实际得到的dat文件中最后记录的一些函数可能只有进入记录,没有对应退出的记录,这样可能造成后面的脚本自动分析失败,这个问题可以对生成的dat文件稍加改造来解决。使用文本编辑器打开dat文件,在文件头添加如下三行信息:
其中第一行“1651 1 0 0 0”为dat格式文件头,是固定不变的。而第二行“0xA0000000”为一个任意数,只要与测试工程中任意一个函数的指针都不相同就行。
接下来就是利用TI提供的自动化脚本分析工具进dat文件进行分析,脚本分析工具文件夹中包含以下相关文件:
(1)func_info.pl.ofd6x.exe和XML_TI_OFD.pm安装TI提供的cg_xml-v2_20_00-Setup.exe之后从安装程序目录下得到的,主要用来根据函数地址生成函数名字符串;(2)cmd.bat是可执行批处理文件,使用时需要修改该文件,将其中的。out和。dat文件名改为与实际处理的文件名一致;(3)post_process7.pl是dat文件分析专用Perl语言脚本。运行该脚本之前,需要安装ActivePerl.
把获取的记录函数进出时间的。dat文件和工程编译生成的。out文件也拷贝到该目录中,执行cmd.bat即生成最终分析结果文件output.
csv.用Excel打开该文件,也可另存为xls文件,方便编辑。
3.实用实例
按照前面的描述定义钩子函数并设置编译器编译选项,在加载DSP并运行相关系统功能后,获得如下某DSP系统函数执行时间测试结果样例,可以清楚的看到实际运行过程中各函数执行占用CPU的情况,CPU占用率比较大的函数就是要着手优化的重点。
如表1所示,测试结果中的“Exclusive_Cycles”是不包括子函数执行时间在内的函数执行占用CPU周期数(净时间);“Inclusive_Cycles”是包括子函数执行时间在内的函数执行占用CPU周期数(毛时间);“Number_of_Calls”在函数执行的次数;“Avg_Cycles”是平均执行cycle数;“Max_Cycles”是最大执行净cycle数;“Min_Cycles”是最小执行净cycle数。
此hook信息记录功能对于BIOS库函数及inline函数,编译器不会嵌入hook调用,测试时根据需要,可以把内联函数的inline关键字去掉。另外,在-o3优化的情况下,为了提高执行效率,编译器会把某些代码行规模小,调用地方少的普通函数自动展开为inline函数(这种情况可以从汇编代码中看到),从而导致这些函数没有被hook记录到。此时,可以将使用了-o3优化的文件或整个工程的编译选项auto inline threshold设置为0,以解决编译器自动inline的问题。
4.结论
使用该进出hook函数进行函数执行时间的自动测试方法,可以轻而易举的得到实际运行中各函数准确的平均执行时间,以及各自对CPU的占用情况,将软件开发人员从之前的机械繁琐的函数效率测试的工作中解放出来,使得函数优化工作能够更加准确的把握到重点,后续优化效果评估也更加的容易,从而大大提高软件函数级优化工作的效率。