嵌入式C語言源代碼優(yōu)化方案(非編譯器優(yōu)化),收藏了!

轉(zhuǎn)自:嵌入式云IOT技術圈
選擇一種合適的數(shù)據(jù)結(jié)構(gòu)很重要,如果在一堆隨機存放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。數(shù)組與指針語句具有十分密切的關系,一般來說,指針比較靈活簡潔,而數(shù)組則比較直觀,容易理解。對于大部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。
for(i=0;i<100;i++){
A=array[i++];
...
}指針運算:
p=array
while(!p){
a=*(p++);
...
}指針方法的優(yōu)點是,array的地址每次裝入地址p后,在每次循環(huán)中只需對p增量操作。在數(shù)組索引方法中,每次循環(huán)中都必須根據(jù)i值求數(shù)組下標的復雜運算。
2、使用盡量小的數(shù)據(jù)類型
能夠使用字符型(char)定義的變量,就不要使用整型(int)變量來定義;能夠使用整型變量定義的變量就不要用長整型(long int),能不使用浮點型(float)變量就不要使用浮點型變量。當然,在定義變量后不要超過變量的作用范圍,如果超過變量的范圍賦值,C編譯器并不報錯,但程序運行結(jié)果卻錯了,而且這樣的錯誤很難發(fā)現(xiàn)。
3、減少運算的強度
(1)查表 (游戲程序員必修課)
舊代碼:
long factorial(int i)
{
if (i == 0)
return 1;
else
return i * factorial(i - 1);
}新代碼:
static long factorial_table[] = {1, 1, 2, 6, 24, 120, 720 /* etc */ };
long factorial(int i)
{
return factorial_table[i];
}如果表很大,不好寫,就寫一個init函數(shù),在循環(huán)外臨時生成表格。
(2)求余運算
a=a%8; 可以改為:a=a&7;
說明:位操作只需一個指令周期即可完成,而大部分的C編譯器的“%”運算均是調(diào)用子程序來完成,代碼長、執(zhí)行速度慢。通常,只要求是求2n方的余數(shù),均可使用位操作的方法來代替。
(3)平方運算
a=pow(a, 2.0); 可以改為:a=a*a;
a=pow(a,3.0); 更改為:a=a*a*a;
則效率的改善更明顯。
(4)用移位實現(xiàn)乘除法運算
a=a*4;
b=b/4;
可以改為:
a=a<<2;
b=b>>2;
采用運算量更小的表達式替換原來的表達式,下面是一個經(jīng)典例子:
舊代碼:
x = w % 8;
y = pow(x, 2.0);
z = y * 33;
for (i = 0;i < MAX;i++)
{
h = 14 * i;
printf("%d", h);
}新代碼:
x = w & 7; /* 位操作比求余運算快*/
y = x * x; /* 乘法比平方運算快*/
z = (y << 5) + y; /* 位移乘法比乘法快 */
for (i = h = 0; i < MAX; i++)
{
h += 14; /* 加法比乘法快 */
printf("%d",h);
}(5)避免不必要的整數(shù)除法
不好的代碼:
int i, j, k, m;
m = i / j / k;
推薦的代碼:
int i, j, k, m;
m = i / (j * k);
(6)使用增量和減量操作符
在使用到加一和減一操作時盡量使用增量和減量操作符,因為增量符語句比賦值語句更快,原因在于對大多數(shù)CPU來說,對內(nèi)存字的增、減量操作不必明顯地使用取內(nèi)存和寫內(nèi)存的指令,比如下面這條語句:
x=x+1;
模仿大多數(shù)微機匯編語言為例,產(chǎn)生的代碼類似于:
move A,x ;把x從內(nèi)存取出存入累加器A
add A,1 ;累加器A加1
store x ;把新值存回x如果使用增量操作符,生成的代碼如下:
incr x ; x加1
顯然,不用取指令和存指令,增、減量操作執(zhí)行的速度加快,同時長度也縮短了。
(7)使用復合賦值表達式
(8)提取公共的子表達式
float a, b, c, d, e, f;
...
e = b * c / d;
f = b / d * a;推薦的代碼:
float a, b, c, d, e, f;
...
const float t(b / d);
e = c * t;
f = a * t;4、結(jié)構(gòu)體成員的布局
很多編譯器有“使結(jié)構(gòu)體字,雙字或四字對齊”的選項。但是,還是需要改善結(jié)構(gòu)體成員的對齊,有些編譯器可能分配給結(jié)構(gòu)體成員空間的順序與他們聲明的不同。但是,有些編譯器并不提供這些功能,或者效果不好。所以,要在付出最少代價的情況下實現(xiàn)最好的結(jié)構(gòu)體和結(jié)構(gòu)體成員對齊,建議采取下列方法:
(1)按數(shù)據(jù)類型的長度排序
(2)把結(jié)構(gòu)體填充成最長類型長度的整倍數(shù)
struct
{
char a[5];
long k;
double x;
} baz;推薦的代碼,新的順序并手動填充了幾個字節(jié):
struct
{
double x;
long k;
char a[5];
char pad[7];
} baz;這個規(guī)則同樣適用于類的成員的布局。
(3)按數(shù)據(jù)類型的長度排序本地變量
當編譯器分配給本地變量空間時,它們的順序和它們在源代碼中聲明的順序一樣,和上一條規(guī)則一樣,應該把長的變量放在短的變量前面。如果第一個變量對齊了,其它變量就會連續(xù)的存放,而且不用填充字節(jié)自然就會對齊。有些編譯器在分配變量時不會自動改變變量順序,有些編譯器不能產(chǎn)生4字節(jié)對齊的棧,所以4字節(jié)可能不對齊。下面這個例子演示了本地變量聲明的重新排序:
不好的代碼,普通順序
short ga, gu, gi;
long foo, bar;
double x, y, z[3];
char a, b;
float baz;推薦的代碼,改進的順序
double z[3];
double x, y;
long foo, bar;
float baz;
short ga, gu, gi;(4)把頻繁使用的指針型參數(shù)拷貝到本地變量
避免在函數(shù)中頻繁使用指針型參數(shù)指向的值。因為編譯器不知道指針之間是否存在沖突,所以指針型參數(shù)往往不能被編譯器優(yōu)化。這樣數(shù)據(jù)不能被存放在寄存器中,而且明顯地占用了內(nèi)存帶寬。注意,很多編譯器有“假設不沖突”優(yōu)化開關(在VC里必須手動添加編譯器命令行/Oa或/Ow),這允許編譯器假設兩個不同的指針總是有不同的內(nèi)容,這樣就不用把指針型參數(shù)保存到本地變量。否則,請在函數(shù)一開始把指針指向的數(shù)據(jù)保存到本地變量。如果需要的話,在函數(shù)結(jié)束前拷貝回去。
不好的代碼:
// 假設 q != r
void isqrt(unsigned long a, unsigned long* q, unsigned long* r)
{
*q = a;
if (a > 0)
{
while (*q > (*r = a / *q))
{
*q = (*q + *r) >> 1;
}
}
*r = a - *q * *q;
}// 假設 q != r
void isqrt(unsigned long a, unsigned long* q, unsigned long* r)
{
unsigned long qq, rr;
qq = a;
if (a > 0)
{
while (qq > (rr = a / qq))
{
qq = (qq + rr) >> 1;
}
}
rr = a - qq * qq;
*q = qq;
*r = rr;
}5、循環(huán)優(yōu)化
(1)充分分解小的循環(huán)
不好的代碼:
// 3D轉(zhuǎn)化:把矢量 V 和 4x4 矩陣 M 相乘
for (i = 0;i < 4;i ++)
{
r[i] = 0;
for (j = 0;j < 4;j ++)
{
r[i] += M[j][i]*V[j];
}
}推薦的代碼:
r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3];
r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3];
r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3];
r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*v[3];(2)提取公共部分
對于一些不需要循環(huán)變量參加運算的任務可以把它們放到循環(huán)外面,這里的任務包括表達式、函數(shù)的調(diào)用、指針運算、數(shù)組訪問等,應該將沒有必要執(zhí)行多次的操作全部集合在一起,放到一個init的初始化程序中進行。
(3)延時函數(shù)
void delay (void)
{
unsigned int i;
for (i=0;i<1000;i++) ;
}將其改為自減延時函數(shù):
void delay (void)
{
unsigned int i;
for (i=1000;i>0;i--) ;
}(4)while循環(huán)和do…while循環(huán)
unsigned int i;
i=0;
while (i<1000)
{
i++;
//用戶程序
}或
unsigned int i;
i=1000;
do
{
i--;
//用戶程序
}
while (i>0);在這兩種循環(huán)中,使用do…while循環(huán)編譯后生成的代碼的長度短于while循環(huán)。
(5)循環(huán)展開
這是經(jīng)典的速度優(yōu)化,但許多編譯程序(如gcc -funroll-loops)能自動完成這個事,所以現(xiàn)在你自己來優(yōu)化這個顯得效果不明顯。
for (i = 0; i < 100; i++)
{
do_stuff(i);
}新代碼:
for (i = 0; i < 100; )
{
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
}(6)循環(huán)嵌套
把相關循環(huán)放到一個循環(huán)里,也會加快速度。
舊代碼:
for (i = 0; i < MAX; i++) /* initialize 2d array to 0's */
for (j = 0; j < MAX; j++)
a[i][j] = 0.0;
for (i = 0; i < MAX; i++) /* put 1's along the diagonal */
a[i][i] = 1.0;新代碼:
for (i = 0; i < MAX; i++) /* initialize 2d array to 0's */
{
for (j = 0; j < MAX; j++)
a[i][j] = 0.0;
a[i][i] = 1.0; /* put 1's along the diagonal */
}(7)Switch語句中根據(jù)發(fā)生頻率來進行case排序
Switch 可能轉(zhuǎn)化成多種不同算法的代碼。其中最常見的是跳轉(zhuǎn)表和比較鏈/樹。當switch用比較鏈的方式轉(zhuǎn)化時,編譯器會產(chǎn)生if-else-if的嵌套代碼,并按照順序進行比較,匹配時就跳轉(zhuǎn)到滿足條件的語句執(zhí)行。所以可以對case的值依照發(fā)生的可能性進行排序,把最有可能的放在第一位,這樣可以提高性能。此外,在case中推薦使用小的連續(xù)的整數(shù),因為在這種情況下,所有的編譯器都可以把switch 轉(zhuǎn)化成跳轉(zhuǎn)表。
不好的代碼:
int days_in_month, short_months, normal_months, long_months;
...
switch (days_in_month)
{
case 28:
case 29:
short_months ++;
break;
case 30:
normal_months ++;
break;
case 31:
long_months ++;
break;
default:
cout << "month has fewer than 28 or more than 31 days" << endl;
break;
}推薦的代碼:
int days_in_month, short_months, normal_months, long_months;
...
switch (days_in_month)
{
case 31:
long_months ++;
break;
case 30:
normal_months ++;
break;
case 28:
case 29:
short_months ++;
break;
default:
cout << "month has fewer than 28 or more than 31 days" << endl;
break;
}(8)將大的switch語句轉(zhuǎn)為嵌套switch語句
當switch語句中的case標號很多時,為了減少比較的次數(shù),明智的做法是把大switch語句轉(zhuǎn)為嵌套switch語句。把發(fā)生頻率高的case 標號放在一個switch語句中,并且是嵌套switch語句的最外層,發(fā)生相對頻率相對低的case標號放在另一個switch語句中。比如,下面的程序段把相對發(fā)生頻率低的情況放在缺省的case標號內(nèi)。
pMsg=ReceiveMessage();
switch (pMsg->type)
{
case FREQUENT_MSG1:
handleFrequentMsg();
break;
case FREQUENT_MSG2:
handleFrequentMsg2();
break;
。。。。。。
case FREQUENT_MSGn:
handleFrequentMsgn();
break;
default: //嵌套部分用來處理不經(jīng)常發(fā)生的消息
switch (pMsg->type)
{
case INFREQUENT_MSG1:
handleInfrequentMsg1();
break;
case INFREQUENT_MSG2:
handleInfrequentMsg2();
break;
。。。。。。
case INFREQUENT_MSGm:
handleInfrequentMsgm();
break;
}
}enum MsgType{Msg1, Msg2, Msg3}
switch (ReceiveMessage()
{
case Msg1;
。。。。。。
case Msg2;
。。。。。
case Msg3;
。。。。。
}為了提高執(zhí)行速度,用下面這段代碼來替換這個上面的switch語句。
/*準備工作*/
int handleMsg1(void);
int handleMsg2(void);
int handleMsg3(void);
/*創(chuàng)建一個函數(shù)指針數(shù)組*/
int (*MsgFunction [])()={handleMsg1, handleMsg2, handleMsg3};
/*用下面這行更有效的代碼來替換switch語句*/
status=MsgFunction[ReceiveMessage()]();(9)循環(huán)轉(zhuǎn)置
有些機器對JNZ(為0轉(zhuǎn)移)有特別的指令處理,速度非???,如果你的循環(huán)對方向不敏感,可以由大向小循環(huán)。
舊代碼:
for (i = 1; i <= MAX; i++)
{
。。。
}新代碼:
i = MAX+1;
while (--i)
{
。。。
}不過千萬注意,如果指針操作使用了i值,這種方法可能引起指針越界的嚴重錯誤(i = MAX+1;)。當然你可以通過對i做加減運算來糾正,但是這樣就起不到加速的作用,除非類似于以下情況:
舊代碼:
char a[MAX+5];
for (i = 1; i <= MAX; i++)
{
*(a+i+4)=0;
}i = MAX+1;
while (--i)
{
*(a+i+4)=0;
}(10)公用代碼塊
一些公用處理模塊,為了滿足各種不同的調(diào)用需要,往往在內(nèi)部采用了大量的if-then-else結(jié)構(gòu),這樣很不好,判斷語句如果太復雜,會消耗大量的時間的,應該盡量減少公用代碼塊的使用。(任何情況下,空間優(yōu)化和時間優(yōu)化都是對立的–東樓)。當然,如果僅僅是一個(3==x)之類的簡單判斷,適當使用一下,也還是允許的。記住,優(yōu)化永遠是追求一種平衡,而不是走極端。
(11)提升循環(huán)的性能
要提升循環(huán)的性能,減少多余的常量計算非常有用(比如,不隨循環(huán)變化的計算)。
for( i 。。。)
{
if( CONSTANT0 )
{
DoWork0( i );// 假設這里不改變CONSTANT0的值
}
else
{
DoWork1( i );// 假設這里不改變CONSTANT0的值
}
}if( CONSTANT0 )
{
for( i 。。。)
{
DoWork0( i );
}
}
else
{
for( i 。。。)
{
DoWork1( i );
}
}(12)選擇好的無限循環(huán)
在編程中,我們常常需要用到無限循環(huán),常用的兩種方法是while (1)和for (;;)。這兩種方法效果完全一樣,但那一種更好呢?然我們看看它們編譯后的代碼:
編譯前:while (1);
編譯后:
mov eax,1
test eax,eax
je foo+23h
jmp foo+18h編譯前:for (;;);
編譯后:jmp foo+23h
顯然,for (;;)指令少,不占用寄存器,而且沒有判斷、跳轉(zhuǎn),比while (1)好。
6、提高CPU的并行性
(1)使用并行代碼
double a[100], sum;
int i;
sum = 0.0f;
for (i=0;i<100;i++)
sum += a[i];double a[100], sum1, sum2, sum3, sum4, sum;
int i;
sum1 = sum2 = sum3 = sum4 = 0.0;
for (i = 0;i < 100;i += 4)
{
sum1 += a[i];
sum2 += a[i+1];
sum3 += a[i+2];
sum4 += a[i+3];
}
sum = (sum4+sum3)+(sum1+sum2);要注意的是:使用4路分解是因為這樣使用了4段流水線浮點加法,浮點加法的每一個段占用一個時鐘周期,保證了最大的資源利用率。
(2)避免沒有必要的讀寫依賴
float x[VECLEN], y[VECLEN], z[VECLEN];
。。。。。。
for (unsigned int k = 1;k < VECLEN;k ++)
{
x[k] = x[k-1] + y[k];
}
for (k = 1;k <VECLEN;k++)
{
x[k] = z[k] * (y[k] - x[k-1]);
}float x[VECLEN], y[VECLEN], z[VECLEN];
。。。。。。
float t(x[0]);
for (unsigned int k = 1;k < VECLEN;k ++)
{
t = t + y[k];
x[k] = t;
}
t = x[0];
for (k = 1;k <;VECLEN;k ++)
{
t = z[k] * (y[k] - t);
x[k] = t;
}7、循環(huán)不變計算
total = a->b->c[4]->aardvark + a->b->c[4]->baboon + a->b->c[4]->cheetah + a->b->c[4]->dog;新代碼:
struct animals * temp = a->b->c[4];
total = temp->aardvark + temp->baboon + temp->cheetah + temp->dog;float a, b, c, d, f, g;
。。。
a = b / c * d;
f = b * g / c;這種寫法當然要得,但是沒有優(yōu)化
float a, b, c, d, f, g;
。。。
a = b / c * d;
f = b / c * g;如果這么寫的話,一個符合ANSI規(guī)范的新的編譯器可以只計算b/c一次,然后將結(jié)果代入第二個式子,節(jié)約了一次除法運算。
8、函數(shù)優(yōu)化
(1)Inline函數(shù)
(2)不定義不使用的返回值
(3)減少函數(shù)調(diào)用參數(shù)
(4)所有函數(shù)都應該有原型定義
一般來說,所有函數(shù)都應該有原型定義。原型定義可以傳達給編譯器更多的可能用于優(yōu)化的信息。
(5)盡可能使用常量(const)
盡可能使用常量(const)。C++ 標準規(guī)定,如果一個const聲明的對象的地址不被獲取,允許編譯器不對它分配儲存空間。這樣可以使代碼更有效率,而且可以生成更好的代碼。
(6)把本地函數(shù)聲明為靜態(tài)的(static)
如果一個函數(shù)只在實現(xiàn)它的文件中被使用,把它聲明為靜態(tài)的(static)以強制使用內(nèi)部連接。否則,默認的情況下會把函數(shù)定義為外部連接。這樣可能會影響某些編譯器的優(yōu)化——比如,自動內(nèi)聯(lián)。
9、采用遞歸
10、變量
在最內(nèi)層循環(huán)避免使用全局變量和靜態(tài)變量,除非你能確定它在循環(huán)周期中不會動態(tài)變化,大多數(shù)編譯器優(yōu)化變量都只有一個辦法,就是將他們置成寄存器變量,而對于動態(tài)變量,它們干脆放棄對整個表達式的優(yōu)化。盡量避免把一個變量地址傳遞給另一個函數(shù),雖然這個還很常用。C語言的編譯器們總是先假定每一個函數(shù)的變量都是內(nèi)部變量,這是由它的機制決定的,在這種情況下,它們的優(yōu)化完成得最好。但是,一旦一個變量有可能被別的函數(shù)改變,這幫兄弟就再也不敢把變量放到寄存器里了,嚴重影響速度。看例子:
a = b();
c(&d);
因為d的地址被c函數(shù)使用,有可能被改變,編譯器不敢把它長時間的放在寄存器里,一旦運行到c(&d),編譯器就把它放回內(nèi)存,如果在循環(huán)里,會造成N次頻繁的在內(nèi)存和寄存器之間讀寫d的動作,眾所周知,CPU在系統(tǒng)總線上的讀寫速度慢得很。比如你的賽楊300,CPU主頻300,總線速度最多66M,為了一個總線讀,CPU可能要等4-5個周期,得。。得。。得。。想起來都打顫。
11、使用嵌套的if結(jié)構(gòu)
說明:上面的優(yōu)化方案由王全明收集整理。
很多資料來源于網(wǎng)上,出處不祥,在此對所有作者一并致謝!
注意:優(yōu)化是有側(cè)重點的,優(yōu)化是一門平衡的藝術,它往往要以犧牲程序的可讀性或者增加代碼長度為代價。

灰常實用的一鍵開關機電路,各位大佬進來mark一下?

非常經(jīng)典的余電快速泄放電路,你學會了嗎?

某大公司非常經(jīng)典的電壓掉電監(jiān)測電路,你學會了嗎?

USB外接電源與鋰電池自動切換電路設計,你GET到精髓了嗎?

分享一個來自蘇泊爾的超低成本隔離交流電壓檢測+掉電檢測二合一電路
點擊閱讀??
