C66x定点浮点混合DSP循环编程优化指南
混合的定点和浮点代码
C66x使用如下指令进行浮点和定点的转换:
DINTHSP, DINTHSPU, DSPINTH: 把一对的16-bit整数转换为一对单精度,带和不带符号扩展。
DINTSP, DINTSPU, DSPINT: 把一对的32-bit整型转换为一对的单精度浮点,带和不带符号扩展。
- for ( i = 0; i < n; i++)
- {
- temp = _amem4(&a);
- a_sqr =(float) ((int) _dotp2(temp, temp));
- dtemp =_dinthsp(temp);
- oneOverAbs_a =_rsqrsp(a_sqr);
- /* 1st interpolation*/
- oneOverAbs_a = oneOverAbs_a * (1.5f - (a_sqr/2.f)*
- oneOverAbs_a *oneOverAbs_a);
- abs_a =(short)(a_sqr * oneOverAbs_a);
- dtemp1 = _ftod(oneOverAbs_a, oneOverAbs_a);
- dtemp1 = _dmpysp(dtemp, dtemp1);
- dtemp1= _dmpysp(_ftod(32768.f, 32768.f), dtemp1);
- _amem4(&ejalpha) =_dspinth(dtemp1);
- }
复制代码
浮点转换成带Q值的16-bit定点的C代码
- / * strip sign */
- sp = 0x7FFFFFFF & _ftoi(input);
- /* shift the 23-bit mantissa to lower 16-bit */
- temp = 0x04C00000 + (head<<23) + (sp & 0xFF800000);
- magic = _itof(0x04C00000 + (head<<23) + (sp & 0xFF800000));
- tempf = input + magic;
- output = _ext(_ftoi(input + magic), 16, 16);
- q = 15 + (127 + 8) - (_ftoi(magic) >> 23);
复制代码
其中head是指headroom,即保留的整数位;
图3. 浮点转定点的动态范围
软件流水优化C66x循环的其他考虑
消除TMS320C66x寄存器不足的压力
尽量避免4-way的SIMD指令来减少寄存器压力,虽然SIMD能有更好的数据处理的并行,但很多SIMD的处理需要4个寄存器作为源和目的操作数,虽然C66x提供了64个寄存器(A侧和B侧各32个),但这种还是会带来寄存器压力的。寄存器不足的常见编译反馈信息如下
- ;* ii = 19 Cannot allocate machine registers
- ;* Regs Live Always : 12/10 (A/B-side)
- ;* Max Regs Live : 38/31
- ;* Max Cond Regs Live : 1/0
复制代码
循环的软件流水给的资源约束在功能单元,但是迭代周期没法在相应的约束内完成,这种提示信息表明寄存器资源不足,因而可以考虑在SIMD指令使用的地方分析是否因为SIMD的引入增大了寄存器压力。因而一个原则是如果循环不被某种功能单元所约束,那么尽量别使用该功能单元的SIMD指令吧。
使用SIMD的move来实现资源平衡
C66x的SIMD move指令能在寄存器间转移数据,需要注意的是如_itoll, _ftod _ito128, _fto128, _dto128 和 _llto128在C66x上对应于非SIMD的move指令。使用这些intrinsic会导致循环对
.L, .S 和 .D单元的约束。因而可以考虑使用SIMD的move指令来代替这些intrinsics,如SIMD intrinsics _dmv(整型) _fdmv(双精度)。对于何时用SIMD的move指令,有如下参考建议:
使用SIMD move如果你需要赋值寄存器到寄存器对;
使用SIMD move如果你确定这些寄存器不会在接下来的指令中使用。
需要注意的是,SIMD的move会增加循环的动态长度。
尽可能避免通用的相同表达式,尤其对于__x128_t类型
对于TMS320C66x编译器而言,那些结果是__x128_t类型的表达式并不会归为相同的表达式,因而可能会重复计算。所以在使用__x128_t数据类型的intrinsic时,尽量吧相同的部分提取出来。如下所示。这种改变不会改变循环的功能功能,但是却能改变性能
- void dprod_vcse(double *restrict inputPtr,double *restrict coefsPtr,int
- nCoefs,double *restrict sumPtr, int nlength) {
- int i, j;
- double sumTemp = 0, sumTemp1 = 0, sumTemp2 = 0, sumTemp3 = 0;
- for(i = 0; i<nlength/4; i++)
- {
- for (j = 0; j < nCoefs; j++)
- {
- sumTemp = _daddsp(sumTemp,_daddsp(_hid128(_cmpysp(inputPtr<span style="font-style: normal;">,coefsPtr</span><span style="font-style: normal;">)),_lod128(_cmpysp(inputPtr</span><span style="font-style: normal;">,coefsPtr</span><span style="font-style: normal;">))));
- sumTemp1 = _daddsp(sumTemp1,_daddsp(_hid128(_cmpysp(inputPtr[i+1],coefsPtr</span><span style="font-style: normal;">)),_lod128(_cmpysp(inputPtr[i+1],coefsPtr</span><span style="font-style: normal;">))));
- sumTemp2 = _daddsp(sumTemp2,_daddsp(_hid128(_cmpysp(inputPtr[i+2],coefsPtr</span><span style="font-style: normal;">)),_lod128(_cmpysp(inputPtr[i+2],coefsPtr</span><span style="font-style: normal;">))));
- sumTemp3 = _daddsp(sumTemp3,_daddsp(_hid128(_cmpysp(inputPtr[i+3],coefsPtr</span><span style="font-style: normal;">)),_lod128(_cmpysp(inputPtr[i+3],coefsPtr</span><span style="font-style: normal;">))));
- }
- sumPtr</span><span style="font-style: normal;"> = sumTemp;
- sumPtr[i+1] = sumTemp1;
- sumPtr[i+2] = sumTemp2;
- sumPtr[i+3] = sumTemp3;
- }
- }</span>
复制代码
修改为
- <span style="font-style: normal;">void dprod_novcse(double *restrict inputPtr,double *restrict coefsPtr,int
- nCoefs,double *restrict sumPtr, int nlength) {
- int i, j;
- double sumTemp = 0, sumTemp1 = 0, sumTemp2 = 0, sumTemp3 = 0;
- __x128_t cmpysp_temp, cmpysp_temp1, cmpysp_temp2, cmpysp_temp3;
- for(i = 0; i<nlength/4; i++)
- {
- for (j = 0; j < nCoefs; j++)
- {
- cmpysp_temp = _cmpysp(inputPtr</span><span style="font-style: normal;">,coefsPtr</span><span style="font-style: normal;">);
- sumTemp = _daddsp(sumTemp, _daddsp(_hid128(cmpysp_temp),
- _lod128(cmpysp_temp)));
- cmpysp_temp1 = _cmpysp(inputPtr[i+1],coefsPtr</span><span style="font-style: normal;">);
- sumTemp1 = _daddsp(sumTemp1, _daddsp(_hid128(cmpysp_temp1),
- _lod128(cmpysp_temp1)));
- cmpysp_temp2 = _cmpysp(inputPtr[i+2],coefsPtr</span><span style="font-style: normal;">);
- sumTemp2 = _daddsp(sumTemp2, _daddsp(_hid128(cmpysp_temp2),
- _lod128(cmpysp_temp2)));
- cmpysp_temp3 = _cmpysp(inputPtr[i+3],coefsPtr</span><span style="font-style: normal;">);
- sumTemp3 = _daddsp(sumTemp3, _daddsp(_hid128(cmpysp_temp3),
- _lod128(cmpysp_temp3)));
- }
- sumPtr</span><span style="font-style: normal;"> = sumTemp;
- sumPtr[i+1] = sumTemp1;
- sumPtr[i+2] = sumTemp2;</span>
- sumPtr[i+3] = sumTemp3;
- }
- }
复制代码
C6000的C代码中的生命周期过长问题
生命周期过长是DSP代码中的常见问题,这是由于流水线相邻阶段的相关性所致。这通常是算法需要在继续下去前保存结果,这就导致了stall。这不同于寄存器生命周期太长问题(register live-too-long)。开发者需要确定哪些C代码中存在这个问题,然后寻求解决方案。图4是一个从D到A的反馈支路,反馈支路导致了两次迭代间的依赖相关性,一个优化方法是duplicate这个反馈支路,然后优化之。图5是用copy-forward方法解决生命周期过长问题。下面是一个解决问题的方法例子。
图4. 使用复制的方式解决生命周期过长问题
图5. 使用copy-forward方法解决生命周期过长问题
- for (i=0; i < N; i++)
- {
- y<span style="font-style: italic;"><span style="font-style: normal;"> = func(x</span><span style="font-style: italic;"><span style="font-style: normal;">);
- }
- Duplicate à
- k = 0;
- for (i=0; i < N; i++)
- {
- y[k++] = func(x</span><span style="font-style: italic;"><span style="font-style: normal;">);
- }
- Copy and Forward
- for (i=0; i < N; i++)
- {
- k = i;
- y[k] = func(x</span><span style="font-style: italic;"><span style="font-style: normal;">);
- } </span></span></span></span></span>
复制代码
C66x DSP是TI最新出的定点和浮点混合DSP,后向兼容C64x+和C67x+、C674x系列DSP。本文介绍了基于C66x架构的常用优化技巧,首先介绍C66x相对于C64x+定点DSP的浮点和定点处理能力的增强,以及C66x新引入的128-bit的数据类型。接下来说明c66x特有的特性和相关的优化技术,重点在其浮点增强以及对复数运算和矩阵、向量运算的intrinsics选择,最后是如何解决寄存器不足、SIMD move的使用平衡寄存器和功能单元的分配以及解决寄存器生命周期过长的问题的高级优化技巧。本文中的编译结果基于CCSv4.1中的CGTools v7.2编译器,编译选项–o3 –s –mw –mv6600。
|