本帖最后由 qwe890asd 于 2018-12-28 16:25 编辑
OMAP平台下H.264的DSP优化
引言
DSP芯片的并行运算能力,使得其十分适合运用在多媒体技术中。X264开源的H.264工程,对H.264源码进行了充分的简化,略去了繁琐切无用的部分。但是X264在移植后还需经过优化才能适应DSP芯片的特点。在达芬奇体系下的多核系统中,通常是从ARM端调用DSP端的编码器。在DSP端的编码器需要进过xDM封装以便编解码服务器进行调用。所以,针对移植后的x264进行DSP端上的优化,是后期达芬奇系统上进行上层应用开发的基础。
1. H.264在DSP端的优化策略
在完成了X264原代码的移植后,只是初步的实现了算法的编码功能,但是效率确十分低下,根本没有办法表现出DSP强大的计算能力和H.264优秀的编码能力,因此对于H.264算法在DSP端还要进行一定的优化,以此来提高编解码效率,为后面视频监控系统的实时性做好保障。
通常对于DSP代码的优化可以分为四个层面,分别是项目级优化、算法优化、指令级优化和缓存优化。
2 项目级优化
使用CCS提供的相关工具和插件,能够加快开发进度。同时利用合理的选择和配置相关的编译器选项,能够使得算法效率提升3倍以上。并且这些优化工具、选项能够根据用户的需求进行灵活的组合。CCS中的优化工具和编译选项也是主要针对音视频处理算法的,所以对于本次设计来说,对其进行合理的配置,显得十分必要。CCS v4中的编译选项在projectàpropertiesàC/C++ Buildàc6000中设置,相关编译器优化选项主要包括:调试模式选项(Debugging Model)、优化等级选项(opt_level)、代码大小选项(opt_for_sapce)、代码速度选项(opt_for_speed)、程序级优化(-op),这些优化编译选项彼此独立,可以根据用户的需求自行组合配置。
下面结合本文分别进行介绍说明。
(1)调试模式选项(Debugging Model)
该选项决定编译出的程序是否支持全字符调试和无任何调试信息等功能。通常使用的是下面两个选项: Ø Full symbolic debug(-g)代表全字符调试。该模式下编译出的程序没做任何级别的优化。一般在最初编写程序的时候使用。该模式下编译出的代码一般大小很大。
Ø Suppress all symbolic debug generation(none) 无调试信息编译选项。编译出的代码不支持任何调试。在最终发布程序的时候选择该选项,因为该选项会使得编译出的程序代码更加精简,能够提升程序的效率。
(2)优化等级选项(opt_level,-o)
优化等级选项主要是对于算法系统进行优化,包括对寄存器、函数、文件等级别进行不同程度的优化。对该选项进行配置后能够使得程序效率相对于为做配置时最多提升4倍。
各项等级选项的作用和功能如表1。
表1 优化等级选项 选项 | 功能 | 意义 | 空置 | 不使用该编译选项 | 不进行优化 | 0 | 简化执行留空图;为变量指定寄存器;
执行循环旋转;去除无用代码;简化定义和语句;内联展开Inline函数
| 启动寄存器级优化 | 1 | 包括-o0等级;执行本地变量、常量复制传递;删除无用的赋值语句;去除本地一般表达式 | 启动本地优化 | 2 | 包括-o1等级;执行软件流水;执行循环优化;去除全局的一般从属性表达语句;去除全局未使用表达式;把循环中的数组引用转换为指针形式;执行循环展开 | 启动函数级优化 | 3 | 包括-o2等级;移除所有从未使用的函数;
用return语句简化未使用的函数;
小函数调用使用内联inline;记录被调用函数名,以便调用函数在优化时能掌握被调函数的属性;当所有调用函数在同一参数位置传递相同的参数值时,传递参数到函数整体内部;标志文件级变量特征。
| 启动文件级优化 |
从表1中可以看出选择-o3,所优化的程度是最深入的,对于提升算法性能是最有效果的。因此尽量使用-o3优化选项。
(3)代码大小优化选项(opt_for_space,-ms)
代码的大小与程序执行的速度相关,一般来说为了提高运行速度,代码大小会比通常更大。现在存储器一般都较大,因此对于代码大小的限制就放的很宽了,所以为了提升速度一般都选择更大的代码尺寸。选项从无到3共有5个选项,代表着代码的大小从大到小。当选择无的时候意味着代码尺寸最大,选择3的时候代码尺寸最小。多数时候选择0。
(4)代码速度选项(opt_for_speed,-mf)
代码执行速度越快,意味着DSP效率越高,在本文中以为着编解码效率的提高。选项从0到5代表着从速度最慢到最快。显然,本文选择5。
(5)程序级优化(op)
程序级优化选项将允许编译器对整个项目的所有源程序联合观测,可以优化程序性能,同时代码的尺寸也得到优化。其对应选项的作用表2所示。 表2 程序级优化选项 选项 | 意义 | 无 | 不进行程序级优化 | 0 | 当前模块调用其他模块定义的变量或者函数 | 1 | 当前模块没有调用其他模块定义的函数 | 2 | 当前模块没有调用其他模块定义的变量或者函数 | 3 | 当前模块没有调用其他模块定义的变量 |
从表2中可以看出,选择“2”是最有利于提升性能的。
(6)其他的优化选项
除去前面的5中主要的优化方式外,还有一些常用的优化选项如下所述。
-mt:表面程序中不存在多个指针同时指向同一块存储空间的情况,消除了存储区域的数据相关性,是的软件流水操作得以更好的运行,提高代码运行的并行性。
-mh:去掉流水线的前序和结语,减小循环体内的代码长度,从而缩短循环体执行的时间。
-mw:在汇编语言文件中加入软件流水信息,该选项对于流水线性能分析十分重要。
综合,上面的所有相关优化选项,和实际中的测试对比,在本次设计中最优化的选择方案是:-o3、-ms0、-mf5、-op2、-mt、-mh、-mw。表3是移植x264成功后的编码测试表。进行测试后,QCIF和CIF的编码性能如表4所示。
表3 移植x264后QCIF和CIF的编码性能
QCIF视频
| 编码帧率(fps)
| CIF视频
| 编码帧率(fps)
| akiyo_qcif.yuv | 6.02 | akiyo_cif.yuv | 1.43 | container_qcif.yuv | 5.08 | container_cif.yuv | 1.20 | foreman_qcif.yuv | 2.60 | foreman_cif.yuv | 0.63 | hall_qcif.yuv | 4.82 | hall_cif.yuv | 1.23 | mother_qcif.yuv | 4.72 | mother_cif.yuv | 1.24 | news_qcif.yuv | 4.19 | news_cif.yuv | 1.01 | 平均 | 4.57 | 平均 | 1.12 |
表4 项目级优化后的编解码效率 QCIF视频
| 编码帧率(fps)
| CIF视频
| 编码帧率(fps)
| akiyo_qcif.yuv | 28.28 | akiyo_cif.yuv | 7.07 | container_qcif.yuv | 24.32 | container_cif.yuv | 6.57 | foreman_qcif.yuv | 10.40 | foreman_cif.yuv | 2.58 | hall_qcif.yuv | 20.71 | hall_cif.yuv | 5.05 | mother_qcif.yuv | 20.43 | mother_cif.yuv | 5.01 | news_qcif.yuv | 21.16 | news_cif.yuv | 4.23 | 平均 | 20.88 | 平均 | 5.09 |
由表4可以看出通过编译器选项优化后的H.264视频编码效率已经得到了极大的提升,对于QCIF格式能到平均20.88fps,对于CIF能达到5.09fps,与表3对比,相对于初次移植完成的时候的4.57fps和1.12fps,效能分别提升了457%和454%。
3 指令级优化
指令级优化主要分为C语言代码优化和改善软件流水。其中C语言优化主要是针对代码中使用的数据类型,消除指令和数据之间的相关性,使用内联(intrinsic)函数。改善软件流水主要是针对循环体中局部变量的使用和避免循环内调用函数等。
3.1优化C语言代码
通过优化C语言代码,有利于CCS中的C语言编译器生成高效率的汇编代码。主要包括以下几个方面的内容。
Ø 选择合适的数据类型
对于系统用到的TMS320C64X系列芯片,其在CCS中选择的C6000编译器支持的数据类型有:字符型(char,8位)、短整型(short,16位)、整型(int,32位)、长整型(long,40位)、浮点型(float,32位)和双精度浮点型(double,64位)。对于数据类型的选择,基本的原则是不使用超过实际运算所需最大的数据长度;另外,对于定点的乘法,最好选用short类型作为乘法器的输入,这样做的好处是能够更加有效的使用乘法器。对于循环变量应该使用整型或者无符号整型。
Ø 消除数据和指令之间的相关性
消除数据和指令之间的相关性,可以使得指令能够并行化运行。其原因在于,只有前后不相关的指令才能够并行处理。在函数入口参数的指针前面使用restrict关键字,能够告诉编译器该函数运行的时候不会产生指针重合的情况。同样对于,执行过程中的常量可以再定义前加上const关键字。
Ø 使用内联(intrinsic)函数
intrinsic指令是CCS提供的在C语言环境下使用汇编指令的技术。内联函数是能够直接映射为汇编指令操作的特殊函数。具有调用方便,同时又兼有高效性的特点。总体来说,一个内联函数在一个时钟周期能够完成的操作通常是普通C函数十多个时钟周期才能完成的工作。在H.264中使用频率较大的相关内联函数如下。
_add4:加法指令,一次执行4对8 位数的加法。1个寄存器有32位,可以存放4个8位数据。计算的时候,2个源寄存器中的4组对应的8位数据分别相加,结果存放在目标寄存器中。
_avgu4:均值运算,一次执行4对8位无符号数据的平均运算。计算的时候,2个源寄存器中的4组8位无符号型紧缩字求平均,结果以4个8位数据分别相加,结果存放在目标寄存器中。
_dotpi4:一次执行4对8位无符号数据点乘运算。计算中,2个源寄存器中的4组8位无符号型紧缩字对应相乘,乘积相加,所得结果存放在32位寄存器中。
_mem4(void *ptr):内存指令读取,一次完成4B数据的读写操作。以一段常规的读取内粗的子函数代码为例,其代码如下
for(j=0;j<block_size;j++)</block_size;j++)
for(i=0;i<block_size;i++)</block_size;i++)
imgY[img->pix_y+block_y+j][img->pix_x+block_x+i]=img->m7[j] | 以上这段代码的作用是进行一个块的数据的复制,读内存指令需要4个时钟周期完成,对于一个4×4块而言,复制16B的数据大约所需的时钟数位(4+1)×16=80个。
使用内联函数_mem4(void *ptr)进行同样的操作,其代码如下:
for(j=0;j<block_size;j++){</block_size;j++){
data=mem4(&(img->m7[j][0]));
_mem4(&imgY[img->pix_y+block_y+j][img->pix_x+block_x+0])=data;
}
|
这个循环只需要(4+1)×4=20个时钟周期,仅仅为原来时钟数的25%。 在H.264中插值、运动估计、DCT变换中有大量块数据复制的使用,使用内联函数可以大大的提高执行的效率。
利用CCS提供的profile选项可以对比优化前后各项编码函数占用的指令周期,比如以8x8预测编码函数predict_8x8c_p( )为例,。选用foreman序列作为参考视频,进行50帧的编码,优化之前predict_8x8c_p( )函数占用的指令周期如图1:
优化之后predict_8x8c_dc( )函数占用的指令周期如图2
如图1和图2,可以看出在调用次数均为3364次的情况下,优化前predict_8x8_p( )占用的总的周期数是774000,优化后是418462,使用内联函数使得效率提升了45.9%。
在使用了_pack()、_spacku4()和_amem4()等内联函数后使得predict_8x8_p( )函数占用的指令周期得到明显的缩短,效率得到明显的提升。
优化前后,其他的相关预测编码函数所占用的指令周期如图3和图4所示。
3.2改善软件流水
视频算法中核心是对图像块做像素点循环,CCS针对这种特定循环for语句,提供了无硬件开销的软件流水(software pipeline)。循环条件判断不再占用指令,软件流水有3个步骤组成,即填充(prolog)、循环核(loop kernel)和排空(epilog),对应的功能是建立循环、使用循环和释放循环。在循环嵌套中,只有最内层的循环才能进行软件流水。 在程序中如果判断跳转过多,或者程序的循环嵌套的深度过大,对于DSP的并行运算能力将会有严重的影响。每个跳转指令大概都有5个延迟间隙,这是十分耗时的,因此在代码中应该尽量的减少程序中的跳转分支。对于多重循环的控制,如果外层循环较少,可将内层循环展开,把转移条件结合起来,以便于减少层与层之间的相互联系。此外,DSP的并行运算效果也反应在它对循环的软件流水分配上。
4 缓存优化
cache缓存是将CPU近期访问过的数据或者程序放置在cache中,以提高CPU的执行速度。OMAP3730中的TMS320C64X系列的DSP芯片采用了两级高速缓存cache结构,能够极大地提高CPU的数据和程序的读写速度。其cache存储结构如图5所示。 第一级cache为片上L1级,以CPU的速度被读写。LI又分为L1P程序Cache和L1D数据Cache,其中L1P、L1D的大小均是32KB。L1P采用直接映射的组织形式,仅做Cache使用,不能映射到C64x存储空间,不支持用户读写,且以CPU 的速度进行访问。L1D采用双路联想组合Cache的组织形式,仅做Cache操作,不能映射到C64x存储空间,不支持用户读写,以CPU的速度进行访问。 第二级Cache为片上的L2。TMS320C64X的L2Cache为256KB,可以配置为SRAM或者SRAM+Cache的组合。当L2一部分配置为SRAM时,映射到C64X的统一的地址空间内,地址从0x00000000开始,CPU可以直接对Cache进行存取操作。L2一部分配置为Cache时,Cache的大小可以从32KB至256KB进行配置,被配置为Cache的L2 SRAM没有映射到存储器空间中,开发的时候不能进行存取。TMS320C64X的L2 Cache可配置为32KB、64KB、128KB和256KB。在视频编码的开发中,通常选择64KB或128KB,本次设计中配置为128KB。
图5 TMS320C64X+的存储结构图
开发的时候不能对TMS320C64X的两级Cache进行直接读写,因为Cache有一套特定的控制机制来完成数据的管理。对于L1D和L1P不需要进行人为干预,Cache控制器有专用的管理机制,优化的重点应该放在L2中。TI对于Cache的控制提供了一套API函数:CACHE_xxx,其中使用较为频繁的是CACHE_clean(),其作用是将Cache中的数据无效化,并且将其强行的转移到SDRAM中,实现情况Cache的作用。此外使用CACHE_enableCaching(CE0/CE1)函数完成SRAM映射为Cache,对于算法性能的提高也很有帮助。 5小结
本文基于移植x264算法工程的基础上,深入的对DSP中的H.264编码进行优化,采取了3中优化方式,分别是项目结优化、指令级优化和缓存优化。优化完成后对于H.264的编码效率有了极大的提升,能够满足接下来视频监控系统中对于视频的实时编码的需求。
|