循序渐进做优化:从C62x到C64x一例(下)
3、优化程序完整性的考虑
上面的优化程序在执行的过程中并没有出现问题,但实际上还是存在一些隐患。主要体现在下面两个方面:
第一,当rounding=1时,要先对其中一个源操作数使用add4进行加1操作,虽然从有些应用场合看,源操作数的取值一般不会达到字节型数据的最大值FF,但这里还是应该使用饱和加法saddu4以保证不会溢出,使程序能够更加严密。
第二是对于使用交叉通道的考虑。为了能够缩短整个函数的执行周期,在编程过程中使用了大量的交叉通道,但是对于C64x系列芯片,在使用交叉通道时,如果使用交叉通道的那个源操作数是前一个周期刚刚被更新过的值,则系统会自动延迟一个指令周期,这是在系统内部进行的,在语言语法上却看不到,而在C62x系列芯片中并不存在这个问题。因此我们在应用C64x系列指令集编写优化程序的过程中必须要注意这个问题,避免这种由交叉通道引起的内部延迟。在上面的程序中,就存在这样的问题,这实际上会使函数实际的执行时间变长。在这里,通过对软件流水编排进行适当的调整就可以避免交叉通道阻塞问题。
基于以上两点,对上面的优化函数进行了修改,最后程序的主要部分(稍注意流水线填充的最后一步10)为:
ldndw .d2t2 *hB_src++[hsrcbstride],hB56788:hB1234
|| [hroundingb]saddu4 .s2 hB56788,cst1b,hB5678
|| [!hroundingb] mv .l2 hB56788,hB5678
|| [!hroundinga] mv .l1x hB1234,hA1234
|| [hroundinga]saddu4 .s1x hB1234,cst1a,hA1234 ;10
hloop: ;循环内核
ldndw .d1t1 *hA_src++[hsrcastride],hA4567:hA0123
|| [hcnt]b .s1 hloop
|| [hroundingb]saddu4 .s2 hD5678,cst1b,hD5678 ;1
ldndw .d2t2 *hB_src++[hsrcbstride],hD5678:hD1234
|| avgu4 .m1 hA1234,hA0123,hA0123
|| avgu4 .m2x hB5678,hA4567,hB4567
|| [!hroundinga] mv .l1x hD1234,hC1234
|| [hroundinga]saddu4 .s1x hD1234,cst1a,hC1234 ;2
ldndw .d1t1 *hA_src++[hsrcastride],hC4567:hC0123
|| avgu4 .m1 hC1234,hC0123,hC0123
|| avgu4 .m2x hD5678,hC4567,hD4567 ;3
[hcnt]ldndw .d2t2 *hB_src++[hsrcbstride],hB56788:hB1234
|| [hroundinga]sub4 .l1 hA0123,cst1a,hA0123
|| [hroundingb]sub4 .l2 hB4567,cst1b,hB4567 ;4
stw .d1t1 hA0123,*hdst++[hdstastride]
|| stw .d2t2 hB4567,*hdstb++[hdstbstride]
|| [hroundinga]sub4 .l1 hC0123,cst1a,hC0123
|| [hroundingb]sub4 .l2 hD4567,cst1b,hD4567
|| [hcnt]sub .s1 hcnt,1,hcnt ;5
stw .d1t1 hC0123,*hdst++[hdstastride]
|| stw .d2t2 hD4567,*hdstb++[hdstbstride]
|| [hroundingb]saddu4 .s2 hB56788,cst1b,hB5678
|| [!hroundingb] mv .l2 hB56788,hB5678
|| [!hroundinga] mv .l1x hB1234,hA1234
|| [hroundinga]saddu4 .s1x hB1234,cst1a,hA1234 ;6
上面的程序就是最终的优化版本,在其中,并没有使用跳转指令bdec,是由于在跳转语句之后的周期中还要用到计数器来进行判断是否需要读取新的数据,因此这时使用了传统的方法(说明:实际上如果这里额外的读取并不越界,这也不是问题)。
在C64x系列芯片中,内存存储体(bank)是以字边界对齐的,如果同一周期内存访问的地址中有3个LSB相同,则会产生bank冲突。由于我们的优化函数中读取数据时使用了ldndw指令,每个周期中只能有一个该类指令;而存储运算结果时在A、B两侧同时使用stw指令存储C语言函数中一次内循环的8个相邻字节,故并不存在bank冲突。
整个程序最终只需要34个周期,可以看到与C6000系列的优化程序相比,执行效率大大提高了。
四、结束语
C64x(VelociTI.2)比C6000公共指令(VelociTI)有多项的扩展,特别是增加了SIMD指令,而基于C64x的实用性优化方法讨论并不多。本文从工作中的实战出发,给出了一些优化的思考路线,例如对特定的算法,什么样的指令(如LDNDW, AVGU4等)可以被引用,为此算法应改做什么样的等价变换,最终协同地达到高效调度。在上面的例子中,基于DM642的优化后执行周期数是基于C6205的1/2-1/3左右,而两者之间的频率关系又是3倍,所以对于特定代码片段的提高将有6倍左右。最后,总结一些函数优化时需要注意的问题:
1. 首先,由于C64x极大的扩展了处理能力,在指令编排的适应性、访问存储器带宽、打包/解包类数据的支持、无边界调整的存储器访问能力以及位操作等方面都有所增强,在编程中应充分利用这些特性。
2. 很重要的是,在进行数据处理时也应充分发挥SIMD指令的优势,该指令常常是该段代码的“灵魂”,前后的指令配合也是围绕它展开,这样可以大大缩短执行的指令周期。
3. 应对算法层次上的优化考虑。在C64x系列指令集基础上进行汇编优化时,应将所要优化的函数尽可能的靠近新增指令集指令的执行表达式,一方面可以快速进行正确性验证,另一方面将C语言函数的表达式转换成可以直接使用这类指令的形式,可以缩短函数的执行周期。此时需要注意的问题是函数表达式的正确性。
4. 完善起见,还要注意C64x系列芯片的内部结构及其对结果产生的影响,在充分利用其资源的同时,还要注意其结构的约束:如1)bank冲突问题,以及2)使用交叉通道时引起的交叉通道阻塞问题,对这两种情况应尽量避免,保证程序执行效率的最大化。
作者和他的团队主要从事DSP系统开发,特别是多媒体编解码算法的性能优化,以及相关的Linux嵌入式应用。获取更多信息请访问:http://www.embeddedcore.com/
作者:张廷廷 钱浙滨 更新日期:2005-06-04
来源:embeddedcore.com
浏览次数:
相关文章
相关评论 发表评论
- No Comments