双重循环多重循环看起来比较复杂,但实际上多重循环优化方法比较简单,就在于一个字:“拆”,一旦完成这一步之后,多重循环就成为单层循环,优化就可以按照普通的单层循环来做了。
多重循环的特点是在优化器优化时只在最内层循环中形成一个pipeline,这样循环语句就不能充分利用C6的软件流水线,而且对于内部循环的次数较少的情况,消耗在prolog(循环填充)和eplog(循环排空)上的cycle数也是不可忽视的。
针对这种状况可以考虑将多重循环拆开形成一个单层循环,可以拆外层循环也可以拆内层循环,一般视具体情况而定。这样就可以充分利用优化器构成的Pipeline。如下例:
[cpp] view plain copy
void fir2(const short input[], const short coefs[], short out[])
{
int i, j;
int sum = 0;
for (i = 0; i < 40; i++)
{
for (j = 0; j < 16; j++)
sum += coefs[j] * input[i + 15 - j];
out[i] = (sum >> 15);
}
}
内层循环循环次数较少,运算量也不大,资源方面只占用了一个乘法器,一个cycle只使用一次乘法器,而事实上我们可以在一个cycle内使用两个乘法器,所以还可以充分利用另外的一个乘法器。因此考虑将内层循环拆开来执行,如下:
[cpp] view plain copy
void fir2_u(const short input[], const short coefs[], short out[])
{
int i, j;
int sum;
for (i = 0; i < 40; i++)
{
sum = coefs[0] * input[i + 15];
sum += coefs[1] * input[i + 14];
sum += coefs[2] * input[i + 13];
sum += coefs[3] * input[i + 12];
sum += coefs[4] * input[i + 11];
sum += coefs[5] * input[i + 10];
sum += coefs[6] * input[i + 9];
sum += coefs[7] * input[i + 8];
sum += coefs[8] * input[i + 7];
sum += coefs[9] * input[i + 6];
sum += coefs[10] * input[i + 5];
sum += coefs[11] * input[i + 4];
sum += coefs[12] * input[i + 3];
sum += coefs[13] * input[i + 2];
sum += coefs[14] * input[i + 1];
sum += coefs[15] * input[i + 0];
out[i] = (sum >> 15);
}
}
这样虽然代码长度增加了,可变成了单循环,所有的运算都参加到pipeline中来,在Piped loop kernal中产生每一个cycle内都使用了两个乘法器,充分利用了DSP内部的资源,提高了运行效率。又如下例:
[cpp] view plain copy
tot = 4;
for (k = 0; k < 4; k++)
{
max = 0;
for (i = k; i < 44; i += STEP)
{
s = 0;
for (j = i; j < 44; j++)
s = L_mac(s, x[j], h[j - i]);//乘加运算相当于_add(s,_mpy(x[j],h[j-i]))
y32[i] = s;
s = L_abs(s);
if (L_sub(s, max) >(Word32) 0)
max = s;
}
tot = L_add(tot, L_shr(max, 1));
}
在这个多层循环中一共有三层循环,而最内层的循环的运算量很小,只有一次乘累加操作,而我们知道C6中一个packet中可以做两个乘累加运算,所以为了增加内部循环的运算,减少外部循环的层数,我们可以将第一层循环的操作拆开,其负责的运算加入到内部循环中,也就是在内层循环中一次做四次的乘累加运算,这样将多次操作形成pipeline,提高了运行效率,优化后的C代码如下:
[cpp] view plain copy
tot = 4;
max0 = 0;
max1 = 0;
max2 = 0;
max3 = 0;
for (i = 0; i < 44; i += STEP) //STEP=4, 11 times cirs
{
//code
for (j = 0; j <= 40 - i; j++)
{
s0 = (Word32)(_sadd(s0, _smpy(hh[j], xx[j + i])));
s1 = (Word32)(_sadd(s1, _smpy(hh[j], xx[j + i + 1])));
s2 = (Word32)(_sadd(s2, _smpy(hh[j], xx[j + i + 2])));
s3 = (Word32)(_sadd(s3, _smpy(hh[j], xx[j + i + 3])));
}
}
//code
源代码:
[cpp] view plain copy
int dotprod(const short *a, const short *b, unsigned int N)
{
int i, sum = 0;
for (i = 0; i < N; i++)
sum += a[i] * b[i];
return sum;
}
改编后代码:
[cpp] view plain copy
int dotprod(const int *a, const int *b, unsigned int N)
{
int i, sum1 = 0, sum2 = 0;
for (i = 0; i < (N >> 1); i++)
{
sum1 += _mpy(a[i], b[i]);
sum2 += _mpyh(a[i], b[i]);
}
return sum1 + sum2;
}
技巧:
在C语言的调试全部通过以后,可以尝试将尽可能多的语句使用intrinsics函数加以改编,尤其在循环体内,这种改编可以大幅度减少执行时间。
四、
1、源代码:
[cpp] view plain copy
void fir_fxd1(short input[], short coefs[], short out[])
{
int i, j;
for (i = 0; i < 40; i++)
{
for (j = 0; j < 16; j++)
out[i * 16 + j] = coefs[j] * input[i + 15 - j];
}
}
2、改编后的代码:
[cpp] view plain copy
void fir_fxd2(const short input[], const short coefs[], short out[])
{
int i, j;
for (i = 0; i < 40; i++)
{
for (j = 0; j < 16; j++)
out[i * 16 + j] = coefs[j] * input[i + 15 - j];
}
}
3、优化方法说明:
C6000编译器如果确定两条指令是不相关的,则安排它们并行执行。 关键字const可以指定一个变量或者一个变量的存储单元保持不变。这有助于帮助编译器确定指令的不相关性。例如上例中,源代码不能并行执行,而结果改编后的代码可以并行执行。
技巧:
使用const可以限定目标,确定存在于循环迭代中的存储器的不相关性。
五、
1、源代码:
[cpp] view plain copy
void vecsum(short *sum, short *in1, short *in2, unsigned int N)
{
int i;
for (i = 0; i < N; i++)
sum[i] = in1[i] + in2[i];
}
2、改编后的代码:
[cpp] view plain copy
void vecsum6(int *sum, const int *in1, const int *in2, unsigned int N)
{
int i;
int sz = N >> 2;//用int型读取short数据,循环次数减半
_nassert(N >= 20);
for (i = 0; i < sz; i += 2)//循环展开i每次加2
{
sum[i] = _add2(in1[i], in2[i]);
sum[i + 1] = _add2(in1[i + 1], in2[i + 1]);
}
}
3、优化方法说明:
源代码中,函数变量的定义是 short *sum, short *in1, short *in2, 改编后的代码函数变量是int *sum, const int *in1, const int *in2, 整数类型由16位改编成32位,这时使用内联指令“_add2”一次可以完成两组16位整数的加法,效率提高一倍。注意这里还使用了关键字const和内联指令_nassert优化源代码。
[cpp] view plain copy
for (i = 0; i < n; i++)
{
max = -32767;
for (j = 0; j < n; j++)
{
if (sub(tmp2[j], max) >= 0)
{
max = tmp2[j];
ix = j;
}
}
tmp2[ix] = -32768;
tmp[i] = ix;
}
2、优化后的程序
[cpp] view plain copy
if (n0>n1) {temp=n0;n0=n1;n1=temp;}
if (n1>n2) {temp=n1;n1=n2;n2=temp;}
if (n2>n3) {temp=n2;n2=n3;n3=temp;}
if (n3>n4) {temp=n3;n3=n4;n4=temp;}
if (n0>n1) {temp=n0;n0=n1;n1=temp;}
if (n1>n2) {temp=n1;n1=n2;n2=temp;}
if (n2>n3) {temp=n2;n2=n3;n3=temp;}
if (n0>n1) {temp=n0;n0=n1;n1=temp;}
if (n1>n2) {return n1;}
3、优化说明
源程序也为一个求中值的问题,由于已知循环次数固定为5,因此将循环展开使用if语句直接求取中值。
value = 0;
for (i = 0; i < no_of_bits; i++)
{
value = shl(value, 1);
bit = *bitstream++;
if (sub(bit, BIT_1) == 0)
value = add(value, 1);
}
return (value);
}
for (i = 0; i < prmno[mode]; i++)
{
prm[i] = Bin2int(bitno[mode][i], bits);
bits += bitno[mode][i];
}
2、优化后的程序
[cpp] view plain copy
value = 0;
bitsp = bits;
bitnop= &bitno[mode][0];
j = *bitnop++;
j1 = *bitnop++;
j2 = *bitnop++;
j3 = *bitnop++;
j4 = *bitnop++;
_nassert(loop[mode]>=35);
for (i = 0; i < loop[mode]; i++)
{
value = value * 2 + *bitsp++;
j--;
if (j == 0)
{
*prm++ = value;
value = 0;
j = j1;
j1 = j2;
j2 = j3;
j3 = j4;
j4 = *bitnop++;
}
}
3、优化说明
源程序按照数据位流定义取出参数,为双重循环结构,优化中采用重新根据位流的bit长度定义循环次数,化简为单重循环,然后优化循环,去除boundary,使pipeline的数目最小。
十一、copy程序的优化
1、源代码:
[cpp] view plain copy
Word16 i;
for (i = 0; i < L; i++)
{
y[i] = x[i];
}
2、改编代码:
(1)要求数组长度能被2整除
[cpp] view plain copy
Word32 i;
Word32 temp;
int *p1 = (int *)&x[0];
int *q1 = (int *)&y[0];
for (i = 0; i < L / 2; i++)
{
temp = *p1++;
*q1++ = temp;
}
(2)要求数组长度能被4整除
[cpp] view plain copy
Word32 i;
Word32 temp1, temp2;
Word32 *pin1, *pin2, *pout1, *pout2;
pin1 = (Word32 *)&x[0];
pin2 = (Word32 *)&x[2];
pout1= (Word32 *)&y[0];
pout2= (Word32 *)&y[2];
for (i = 0; i < L / 4; i++)
{
temp1 = *pin1;
temp2 = *pin2;
pin1 += 2;
pin2 += 2;
*pout1 = temp1;
*pout2 = temp2;
pout1 += 2;
pout2 += 2;
}
3、优化方法说明:
把一次循环拷贝一个word16的数改为一次循环拷贝2个word16或4个word16的数。
技巧:
充分利用c6xx一次读取32位数的特性,并利用一个指令周期能读取两个数据的特点。
十二、set_zero程序的优化
1、源代码:
[cpp] view plain copy
Word16 i;
for (i = 0; i < L; i++)
{
x[i] = 0;
}
2、改编代码:
(1)数组长度能被2整除
[cpp] view plain copy
Word32 i;
int *x1 = (int *)&x[0];
for (i = 0; i < L / 2; i++)
{
*x1++ = 0;
}
(2)数组长度能被4整除
[cpp] view plain copy
Word32 i;
int *x1 = (int *)&x[0];
int *x2 = (int *)&x[2];
for (i = 0; i < L / 4; i++)
{
*x1 = 0;
*x2 = 0;
x1++;
x2++;
x1++;
x2++;
}
3、优化方法说明:
把一次循环为一个word16的数赋值改为一次为2个或4个word16的数赋值。
//////////////////////////////////////////////////////////////////////////////////自己写的一段性能很高的代码///////////////////////////
[cpp] view plain copy
#include
#define INTRINSIC
short add(short var1, short var2)
{
short var_out;
int L_somme;
L_somme = (int)var1 + var2;
return(var_out);
}
int main()
{
int i, result;
#ifdef INTRINSIC
for (i = 0; i<1000; i++)
{
result = _sadd(100000, 20);
result>0X00007fff ? result = 0x7fff : (result < 0x8000 ? result = 0x8000 : 0);
}
#else
for (i = 0; i < 1000; i++)
add(10, 20);
#endif
return 0;
}