DSP 優化心得
1 推薦 C6XX優化經驗總結 一、c6x的編譯的常用選項 (一)c6x的編譯程式為“cl6x.exe”使用的方法 Cl6x [options] [filenames] Cl6x: 編譯程式 Options: 編譯選項 Filenames: C或彙編源檔案 說明: 編譯選項是一個字母或者兩個字母,對大小寫不敏感。 編譯選項的前面需要有一個“-”符号。 一個字母的選項可以合并在一起。比如“-sgq”與“-s -g -q”相同。 兩個字母的選項如果第一個字母相同也可以合并在一起。比如“-mgt”與“-mg -mt”相同。 (二)有關優化的選項 -mt:表示在程式中沒有使用alaising技術,這使得編譯器可以進行比較好的優化。 -o3:對檔案級别進行最強的優化,一般在編譯時應該使用這個選項。但是在個别情況下使用這個選項優化程式可能會出現 錯誤(-o2有相同現象,-o0和-o1不會出現錯誤)。可能是在優化循環,組織流水線的時候發生錯誤。如果有這種現象出現可以同時 使用-g選項,程式優化就不會出現錯誤,但是優化效果會下降。另外可以調整程式的表達方式,可能會避免編譯器發生錯誤。 -pm:在程式級别進行優化。可以将是以檔案聯合在一起進行優化,主要有去掉沒有被調用的函數、總是常數的變量以及沒有使用的 函數傳回值。建議由程式員自己進行這種優化工作。使用這個選項在win98下編譯可能會出現找不到編譯程式的情況。 -ms0:不使用備援循環進行優化,減小程式的大小。一般情況下這個選項對程式大小的優化作用不明顯。 -mh[n]:去掉流水線的epilog,減小程式的大小。這個選項的作用比較明顯。但是有可能出現讀取位址超出有效範圍的問題, 是以要在資料段的開始和結尾處增加一些pading,或者在配置設定記憶體時保證數組的前面和後面一段範圍内都是有效的位址。 可選的參數n給出這種pading的長度位元組數。 (三)保留編譯和優化資訊的選項 -k:保留優化後生成彙編語言檔案。 -s:彙編語言檔案中加入優化資訊,如果沒有則加入C語言源程式作為注釋。 -mw:在彙編語言檔案加入軟體流水線資訊。 (四)有關調試和剖析的選項 -g:允許符号調試,在“out”檔案中包含符号資訊和行号資訊,可以在c語言級别進行調試和剖析。使用聯合使用-g、-mt和-o3可以保 證能夠進行符号調試的情況下最大限度的優化。 -mg:允許profile優化後的程式。 在“out”檔案中包含符号資訊和很少的行号資訊。允許在c語言的函數基本進行剖析。 如果聯合使用這兩個選項,-g選項可能被忽略,結果與隻用-mg相同。 (五)其它類型 -mln:生成大記憶體模式的程式。 -ml0:預設情況下将集合變量(數組和結構)作為far型。 -ml1:預設情況下将全部函數作為far型 -ml2: 等于-ml0加-ml1 -ml3: 預設情況下将全部資料和函數作為far型 (六)建議使用的編譯方式 Cl6x -gk -mt -o3 -mw -ss “filename” 方式1用于程式的調試,這種方式具有比較強的優化能力,并且支援符号調試。在編譯的過程中不會發生錯誤。 由于生成的“out”檔案中包含了符号資訊和行号資訊,是以比較大。 Cl6x -k -mgt -o3 -mw -ss “filename” 方式2用于程式的剖析(profile),這種方式的優化能力幾乎最強(絕大多數情況下與方式3相同), 并且支援對程式進行profile。檔案中隻包含了符号資訊和很少的行号資訊,是以“out”檔案比較小。 Cl6x -k -mt -o3 -mw -ss “filename” 方式3用于最終的發行版本程式,可以對程式進行最強的優化,并且去掉了全部的符号和行号資訊,是以“out”檔案比較小。 由多個檔案組成的程式應該編寫makefile,将編譯參數放在該檔案中,并在其中說明使用的編譯器的版本号。 (七)連接配接參數 -heap:指定堆的大小 -stack: 指定棧的大小 連接配接的各種選項應該統一放在“cmd”檔案中 二、雙重循環和多重循環的優化總結 雙重循環多重循環看起來比較複雜,但實際上多重循環優化方法比較簡單,就在于一個字:“拆”,一旦完成這一步之後, 多重循環就成為單層循環,優化就可以按照普通的單層循環來做了。 多重循環的特點是在優化器優化時隻在最内層循環中形成一個pipeline,這樣循環語句就不能充分利用C6的軟體流水線, 而且對于内部循環的次數較少的情況,消耗在prolog和eplog上的cycle數也是不可忽視的。 針對這種狀況可以考慮将多重循環拆開形成一個單層循環,可以拆外層循環也可以拆内層循環, 一般視具體情況而定。這樣就可以充分利用優化器構成的Pipeline。如下例: 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内使用兩個乘法器,是以還可以充分利用另外的一個乘法器。是以考慮将内層循環拆開來執行,如下: 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内部的資源,提高了運作效率。又如下例: 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]); 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代碼如下: 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 CCS的優化: 三、16位變為32位操作,使用intrinsic函數,用const等。 1、源代碼: Word32 L_mpy_ll(Word32 L_var1, Word32 L_var2) { double aReg; Word32 lvar; aReg = (double)(0xffff & L_var1) * (double)(0xffff & L_var2) * 2.0; aReg = (aReg / 65536); aReg = floor(aReg); aReg += (double)(0xffff & L_var1) * ((double)L_shr(L_var2,16)) * 2.0; aReg += (double)(0xffff & L_var2) * ((double)L_shr(L_var1,16)) * 2.0; aReg = (aReg / 65536); aReg = floor(aReg); aReg += (double)(L_shr(L_var1,16)) * (double)(L_shr(L_var2,16)) * 2.0; lvar = L_saturate(aReg); return(lvar); } 2、改編後的代碼: static inline Word32 L_mpy_ll(Word32 L_var1, Word32 L_var2) { Word32 aReg_hh; Word40 aReg,aReg_ll,aReg_lh,aReg_hl; aReg_ll = (Word40)_mpyu(L_var1, L_var2)>>16; aReg_lh = (Word40)_mpyluhs(L_var1, L_var2); aReg_hl = (Word40)_mpyhslu(L_var1, L_var2); aReg_hh = _smpyh(L_var1, L_var2); aReg = _lsadd(aReg_ll, _lsadd(aReg_lh, aReg_hl)); aReg = _lsadd(aReg>>15, aReg_hh); return(_sat(aReg)); } 3、優化方法說明: C6000編譯器提供的intrinsic 可快速優化C代碼,intrinsic用前下劃線表示同調用函數一樣可以調用它,即直接内聯為C6000的函數。 例如,在上例的源代碼中沒有使用intrinsics,每一行C代碼需多個指令周期,在改編後的代碼中,每一行代碼僅需一個指令周期。 例如, “aReg_ll = (Word40)_mpyu(L_var1, L_var2)>>16”中“_mpyu”就是一個intrinsics函數,它表示兩個無符号數的高16位相乘, 結果傳回。C6000支援的所有intrinsics指令及其功能參見《TMS320C6000系列DSP的原理與應用》一書的第265、266頁, 該書還提供了另外的例子。這些内聯函數定義在CCS所在的C6000/CGTOOLS/Include目錄下的C6X.h檔案中。 下面這個例子是C6000的“Programmer's Guide”上提取的使用intrinsics優化C代碼的例子。 源代碼: 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; } 改編後代碼: 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、源代碼: 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、改編後的代碼: 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可以指定一個變量或者一個變量的存儲單元保持不變。 這有助于幫助編譯器确定指令的不相關性。例如上例中,源代碼不能并行執行,而結果改編後的代碼可以并行執行。 4、技巧: 使用const可以限定目标,确定存在于循環疊代中的存儲器的不相關性。 五、 1、源代碼: 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、改編後的代碼: void vecsum6(int *sum, const int *in1, const int *in2, unsigned int N) { int i; int sz = N >> 2; _nassert(N >= 20); for (i = 0; i < sz; 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優化源代碼。 4、技巧: 用内聯指令_add2、_mpyhl、_mpylh完成兩組16位數的加法和乘法,效率比單純16位數的加法和乘法提高一倍。 六、if...else...語句的優化 (一) 1、源代碼: if (sub (ltpg, LTP_GAIN_THR1) <= 0) { adapt = 0; } else { if (sub (ltpg, LTP_GAIN_THR2) <= 0) { adapt = 1; } else { adapt = 2; } } 2、改編後的代碼: adapt = (ltpg>LTP_GAIN_THR1) + (ltpg>LTP_GAIN_THR2); (二) 1、源代碼: if (adapt == 0) { if (filt>5443) { result = 0; } else { if (filt < 0) { result = 16384; } else { filt = _sshl (filt, 18)>>16; // Q15 result = _ssub (16384, _smpy(24660, filt)>>16); } } } else { result = 0; } 2、改編後的代碼: filt1 = _sshl (filt, 18)>>16; tmp = _smpy(24660, filt1)>>16; result = _ssub(16384, tmp * (filt>=0)); result = result * (!((adapt!=0)||(filt>5443))); (三) 1、源代碼: static Word16 saturate(Word32 L_var1) { Word16 swOut; if (L_var1 > SW_MAX) { swOut = SW_MAX; giOverflow = 1; } else if (L_var1 < SW_MIN) { swOut = SW_MIN; giOverflow = 1; } else swOut = (Word16) L_var1; return (swOut); } 2、改編後的代碼: static inline Word32 L_shl(Word32 a,Word16 b) { return ((Word32)((b) < 0 ? (Word32)(a) >> (-(b)) : _sshl((a),(b)))) ; } 3、優化方法說明: 如果在循環中出現if...else...語句,由于if...else...語句中有跳轉指令,而每個跳轉指令有5個延遲間隙, 是以程式執行時間延長;另外,循環内跳轉也使軟體流水受到阻塞。直接使用邏輯判斷語句可以去除不必要的跳轉。 例如在例1的源代碼最多有兩次跳轉,而改編後不存在跳轉。例2和例3同樣也去掉了跳轉。 4、技巧: 盡可能地用邏輯判斷語句替代if...else...語句,減少跳轉語句。 七、 1、源程式 dm = 0x7FFF; for (j = 0; j < nsiz[m]; j = add(j, 1)) { if (d[j] <= dm) { dm = d[j]; jj = j; } } index[m] = jj; 2、優化後的程式 dm0 = dm1 = 0x7fff; d0 = (Word16 *)&d[0]; d1 = (Word16 *)&d[1]; # pragma MUST_ITERATE(32,256,64); for (j = 0; j < Nsiz; j+=2) { n0 = *d0; d0 += 2; n1 = *d1; d1 += 2; if (n0 <= dm0) { dm0 = n0; jj0 = j; } if (n1 <= dm1) { dm1 = n1; jj1 = j+1; } } if (dm1 != dm0) { index[m] = (dm1 < dm0)? jj1:jj0; } else { index[m] = (jj1 > jj0)? jj1:jj0; } 3、優化說明 求數組的最小值程式,優化時為了提高程式效率在一個循環之内計算N=1,3,5..和n=2,4,6...的最小值, 然後在比較二者的大小以求得整個數組的最小值。 八、 1、源程式 for (k = 0; k < NB_PULSE; k++) { i = codvec[k]; j = sign[i]; index = mult(i, Q15_1_5); track = sub(i, extract_l(L_shr(L_mult(index, 5), 1))); if (j > 0) { if (i < l_subfr) code[i] = add(code[i], 4096); codvec[k] += (2 * L_SUBFR); } else { if (i < l_subfr) code[i] = sub(code[i], 4096); index = add(index, 16); } if (indx[track] < 0) { indx[track] = index; } else { if (((index ^ indx[track]) & 16) == 0) { if (sub(indx[track], index) <= 0) { indx[track] = shl((indx[track] & 16), 3) + shr(extract_l(L_mult((indx[track] & 15), NB_POS)), 1) + (index & 15); } else { indx[track] = shl((index & 16), 3) + shr(extract_l(L_mult((index & 15), NB_POS)), 1) + (indx[track] & 15); } } else { if (sub((indx[track] & 15), (index & 15)) <= 0) { indx[track] = shl((index & 16), 3) + shr(extract_l(L_mult((index & 15), NB_POS)), 1) + (indx[track] & 15); } else { indx[track] = shl((indx[track] & 16), 3) + shr(extract_l(L_mult((indx[track] & 15), NB_POS)), 1) + (index & 15); } } } } 2、優化後的程式 for (k = 0; k < 8; k++) { i = codvec[k]; j = sign[i]; index = _smpy(i, 6554)>>16; track = i - index*5; con = (j > 0); codvec[k] = codvec[k] + 110*con; index = index + (!con)*16; conn = (i < l_subfr); cono = (j > 0)? 1:-1; code[i] = code[i] + 4096*conn*cono; n0 = index; t0 = indx[track]; n1 = n0&16; t1 = t0&16; n2 = n0&15; t2 = t0&15; tmp0 = (_sshl(n1,19)>>16) + n2*NB_POS + t2; tmp1 = (_sshl(t1,19)>>16) + t2*NB_POS + n2; conp = (((n1 == t1)&&(t0 > n0))||((n1 != t1)&&(t2 <= n2))); tmp = conp*tmp0 + (!conp)*tmp1; if (t0 < 0) indx[track] = n0; else indx[track] = tmp; } 3、優化說明 源程式中在循環中含有許多的if結構,在優化時對if結構首先進行化簡, 再将化簡後的if結構用條件運算表達式進行改寫,最後使循環可以Pipeline。 九、 1、源程式 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、優化後的程式 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語句直接求取中值。 十、 1、源程式 static Word16 Bin2int (Word16 no_of_bits, Word16 *bitstream) { Word16 value, i, bit; 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、優化後的程式 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、源代碼: Word16 i; for (i = 0; i < L; i++) { y[i] = x[i]; } 2、改編代碼: (1)要求數組長度能被2整除 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整除 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的數。 4、技巧: 充分利用c6xx一次讀取32位數的特性,并利用一個指令周期能讀取兩個資料的特點。 十二、set_zero程式的優化 1、源代碼: Word16 i; for (i = 0; i < L; i++) { x[i] = 0; } 2、改編代碼: (1)數組長度能被2整除 Word32 i; int *x1 = (int *)&x[0]; for (i = 0; i < L/2; i++) { *x1++ = 0; } (2)數組長度能被4整除 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的數指派。 4、技巧: 充分利用C6XX一次讀取32位數的特點,并利用一個指令周期能讀取兩個資料的特點。 十三、32bit數與16bit數相乘 1、源代碼: L_tmp0 = Mac_32_16(L_32, hi1, lo1, lo2); 2、改編代碼: L_tmp0=_sadd(_sadd(_smpyhl(hl32, lo2), (_mpyus(hl32, lo2)>>16)<<1), L_32); 3、優化方法說明: hl32是32bit的數,hi1和lo1是16bit的數,且 hl32 = hi 1<<16 + lo1 << 1 ,即hi1和lo1分别是hl32的高16位數和低16位數。 函數Mac_32_16(L_32, hi1, lo1, lo2)實作 L_32 = L_32 + (hi1*lo2)<<1 + ((lo1*lo2)>>15)<<1 源代碼是把一個32位的數拆成兩個16位的數與一個16位的數相乘,優化後的代碼不拆開32位的數, 直接用32位的數與16位的數相乘。運用這種方法必須保證hl32的最低一位數必須為0,否則應用指令_clr(hl32, 0, 0)把 最低位清零。 4、技巧: 源代碼中的低16位數lo1是hl32的低16位右移一位得到的(留出一位符号位)。在與lo2相乘時又右移了15位, 是以在改編代碼中右移16位,并且是以無符号數與lo2相乘。 十四、32bit數與32bit數相乘 1、源代碼: L_tmp = Mac_32 (L_32, hi1, lo1, hi2, lo2); 2、改編代碼: L_tmp = _sadd(_sadd(_smpyh(hl1_32, hl2_32), ((_mpyhslu(hl1_32, hl2_32)>>16)<<1)+ ((_mpyhslu(hl2_32, hl1_32)>>16)<<1)), L_32); 3、優化方法說明: 兩個32位的數相乘,不必分成四個16位的數相乘,直接用32位相乘。其中: hl1_32 = hi1<<16 + lo1<<1, hl2_32 = hi2 <<16 + lo2 <<1 。 源代碼實作: L_32 = L_32 + (hi1*hi2)<<1 + ( (hi1*lo2)>>15 + (lo1*hi2)>>15 )<<1 4、技巧: 低16位與高16位相乘時,低16位使用的是無符号數。 十五、16位除法的優化 1、源代碼: Word16 div_s (Word16 var1, Word16 var2) //實作 var1/var2 { Word16 var_out = 0; Word16 iteration; Word32 L_num = (Word32)var1; Word32 L_denom = (Word32)var2; for (iteration = 0; iteration < 15; iteration++) { var_out <<= 1; L_num <<= 1; if (L_num >= L_denom) { L_num = L_sub (L_num, L_denom); var_out = add (var_out, 1); } } return (var_out); } 2、改編代碼: Word16 div_s1 (Word16 var1, Word16 var2) { Word32 var1int; Word32 var2int; var1int = var1 << 16; var2int = var2 << 15; var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); var1int = _subc(var1int,var2int); return (var1int & 0xffff); } 3、優化方法說明: 實作16位的除法,要求被除數var1和除數var2都是整數,且var1<=var2。利用C6XX特有的指令subc,實作除法的循環移位相減操作。 4、技巧: 把被除數和除數都轉換成32位數來操作,傳回時取低16位數。 十六、C6X優化inline舉例: 1、原程式: for (i = LO_CHAN; i <= HI_CHAN; i++) { norm_shift = norm_l(st->ch_noise[i]); Ltmp = L_shl(st->ch_noise[i], norm_shift); norm_shift1 = norm_l(st->ch_enrg[i]); Ltmp3 = L_shl1(st->ch_enrg[i], norm_shift1 - 1); Ltmp2 = L_divide(Ltmp3, Ltmp); Ltmp2 = L_shr(Ltmp2, 27 - 1 + norm_shift1 - norm_shift); // * scaled as 27,4 * if (Ltmp2 == 0) Ltmp2 = 1; Ltmp1 = fnLog10(Ltmp2); Ltmp3 = L_add(Ltmp1, LOG_OFFSET - 80807124); // * -round(log10(2^4)*2^26 * Ltmp2 = L_mult(TEN_S5_10, extract_h(Ltmp3)); if (Ltmp2 < 0) Ltmp2 = 0; // * 0.1875 scaled as 10,21 * Ltmp1 = L_add(Ltmp2, CONST_0_1875_S10_21); // * tmp / 0.375 2.667 scaled as 5,10, Ltmp is scaled 15,16 * Ltmp = L_mult(extract_h(Ltmp1), CONST_2_667_S5_10); ch_snr[i] = extract_h(Ltmp); } */ 2、優化後程式: //因循環體太大,拆成兩個循環并把相應的函數内嵌以使程式能pipeline, //用L_div_tmp[]儲存因拆分而産生的中間變量。 for (i = LO_CHAN; i <= HI_CHAN; i++) { //norm_shift = norm_l(st->ch_noise[i]); norm_shift = _norm(st->ch_noise[i]); Ltmp = _sshl(st->ch_noise[i], norm_shift); //norm_shift1 = norm_l(st->ch_enrg[i]); norm_shift1 = _norm(st->ch_enrg[i]); //Ltmp3 = L_shl1(st->ch_enrg[i], norm_shift1 - 1); LLtmp1 = st->ch_enrg[i]; LLtmp1 = LLtmp1 << (norm_shift1 + 7); Ltmp3 = (Word32)(LLtmp1 >> 8); Ltmp2 = IL_divide(Ltmp3, Ltmp); //Ltmp2 = L_shr(Ltmp2, 27 - 1 + norm_shift1 - norm_shift); Ltmp2 = (Ltmp2 >> (27 - 1 + norm_shift1 - norm_shift)); if (Ltmp2 == 0) Ltmp2 = 1; L_div_tmp[i] = Ltmp2; } for (i = LO_CHAN; i <= HI_CHAN; i++) { Ltmp2 = L_div_tmp[i]; Ltmp1 = IfnLog10(Ltmp2); //Ltmp3 = L_add(Ltmp1, LOG_OFFSET - 80807124); Ltmp3 = _sadd(Ltmp1, LOG_OFFSET - 80807124); //Ltmp2 = L_mult(TEN_S5_10, extract_h(Ltmp3)); Ltmp2 = _smpy(TEN_S5_10, (Ltmp3 >> 16)); if (Ltmp2 < 0) Ltmp2 = 0; Ltmp1 = _sadd(Ltmp2, CONST_0_1875_S10_21); //Ltmp = L_mult(extract_h(Ltmp1), CONST_2_667_S5_10); Ltmp = _smpy((Ltmp1 >> 16), CONST_2_667_S5_10); //ch_snr[i] = extract_h(Ltmp); ch_snr[i] = (Ltmp >> 16); } 3、優化說明 觀察上面這個循環,循環體本身比較大,且含有兩個函數L_divide()和 fnLog10(),而C62内部隻有32個寄存器,且有些寄存器是系統用的,如B14、B15這樣循環體太大将會導緻寄存器不夠配置設定, 進而導緻系統編譯器無法實作循環的pipeline。 為了實作循環的pipeline。我們需要把循環體進行拆分,拆分時要考慮以下幾點: (1)、拆分成幾個循環比較合适?在各個循環能pipeline的前提下,拆開的循環個數越少越好。這就要求盡可能讓各個 循環的運算量接近。 (2)考慮在什麼地方把程式拆開比較合适?循環體裡的資料流往往并不是單一的,在拆開的斷點處勢必要用中間變量保 存上次的循環運算結果,供以後的循環用。适當的拆開循環體,使所需的中間變量越少越好。 (3)循環體中的函數調用必須定義成内嵌形式,含有函數調用的循環系統是無法使之pipeline的;各個循環體中的判斷分支 機構不可太多,否則系統也無法使之pipeline,為此應近可能把可以确定下來的分支确定下來,并盡可能用内嵌指令。 針對上面這個例子,考慮: (1)為讓各個循環的運算量大緻相當,應把L_divide()和fnLog10()分到兩個循環中去,從循環體大小上考慮, 估計拆成兩個循環比較合适。 (2)考慮在什麼地方把程式拆開比較合适?在 if (Ltmp2 == 0) Ltmp2 = 1; 後拆開,因為後面用到的資料隻有Ltmp2,故隻需用一個數組儲存每次循環的Ltmp2值即可。 (3)循環體中的兩處函數調用L_divide()和fnLog10()都定義了其内嵌形式,IL_divide()和IfnLog10()。 當把可以确定下來的分支作确定處理,并盡可能用内嵌指令後,該循環體中所剩的分支結構已很少,循環體可以pipeline。 優化前程式用2676 cycle,優化後用400 cycle。優化後兩個子循環的MII分别為14和6cycle。 記憶體位址形式: 奔騰,C6000都是32位計算機,字長32,但記憶體位址都是按位元組組織的 一個字4位元組(檢視記憶體時候各個字 時候:例如兩個連續字ox1000 ox1004) 寫彙程式設計式時候,下一個字也需要+4,但寫 C語言時候,int 型,+1就是加4 但是,在Tiger SHARC中,雖然也是32位機,但記憶體是位址是按字組織的,檢視記憶體時,連續的字位址相差1 //自己寫的一段性能很高的代碼/// #i nclude <stdio.h> #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; } |