嵌入式C語言最佳化技巧
嵌入式系統是指完成一種或幾種特定功能的計算機系統,具有自動化程度高,響應速度快等優點,目前已廣泛應用於消費電子,工業控制等領域。下面小編給大家介紹如何嵌入式C語言最佳化技巧,歡迎閱讀!
嵌入式C語言最佳化技巧
1、嵌入式C語言的特點
作為一種結構化程式設計語言,C 語言兼顧多種高階語言的特點,具有很強的功能性和可移植性。但在嵌入式系統開發中,出於對低價產品的需求,系統的計算能力和儲存容量都非常有限,因此如何利用好這些資源就顯得十分重要。開發人員應注意嵌入式 C語言和標準 C 語言的區別,減少生成程式碼長度,提高程式執行效率,在程式設計中對程式碼進行最佳化。
2、C程式碼在程式中的最佳化
現在的 C 編譯器會自動對程式碼進行最佳化,但這些最佳化是對執行速度和程式碼長度的平衡。如果要獲得更小且執行效率更高的程式碼,需要程式設計師手工對程式碼進行最佳化。
3、變數型別的定義
不同的資料型別所生成的機器程式碼長度相差很多,變數型別選取的範圍越小執行速度越快,佔用的記憶體越少。能夠使用字元型(char)定義的變數,就不要使用整型(int)變數來定義;能夠使用整型變數定義的變數就不要用長整型(long int),能不使用浮點型(float)變數就不要使用浮點型變數。相同型別的資料型別,有無符號對機器程式碼長度也有影響。因此我們應按照實際需要合理的選用資料型別。當然,在定義變數後不要超過變數的作用範圍,如果超過變數的範圍賦值,C編譯器並不報錯,但程式執行結果卻錯了,而且這樣的錯誤很難發現。
4、演算法最佳化
演算法最佳化指對程式時空複雜度的最佳化:在 PC 機上進行程式設計時一般不必過多關注程式程式碼的長短,只需考慮功能的實現,但嵌入式系統就必須考慮系統的硬體資源,在程式設計時,應儘量採用生成程式碼短的演算法,在不影響程式功能實現的情況下最佳化演算法。
5、適當的使用宏
在C程式中使用宏程式碼可以提高程式的執行效率。宏程式碼本身不是函式,但使用起來像函式。函式呼叫要使用系統的棧來儲存資料,同時 CPU 在函式呼叫時需要儲存和恢復當前的現場,進行進棧和出棧操作,所以函式呼叫也需要 CPU時間。而宏定義就沒有這個問題:宏定義僅僅作為預先寫好的程式碼嵌入到當前程式中,不產生函式呼叫,所佔用的僅僅是一些空間,省去了引數壓棧,生成組合語言的 call 呼叫,返回引數,執行 return等過程,從而提高了程式的執行速度。雖然宏破壞了程式的可讀性,使排錯更加麻煩,但對於嵌入式系統,為了達到要求的效能,嵌入程式碼常常是必須的做法。
此外,我們還要避免不必要的函式呼叫,請看下面的程式碼:
[plain] view plain copy print?
void str_print( char *str )
{
int i;
for ( i = 0; i < strlen ( str ); i++ )
{
printf("%c",str[ i ] );
}
}
void str_print1 ( char *str )
{
int len;
len = strlen ( str );
for ( i = 0; i < len; i++ )
{
printf("%c",str[ i ] );
}
}
請注意,這兩個函式的功能相似。然而,第一個函式呼叫strlen函式多次,而第二個函式只調用函式strlen一次。因此第二個函式效能明顯比第一個好。
6、內嵌彙編
程式中對時間要求苛刻的部分可以用內嵌彙編來重寫,以帶來速度上的顯著提高。但是,開發和測試彙編程式碼是一件辛苦的工作,它將花費更長的時間,因而要慎重選擇要用匯編的部分。在程式中,存在一個80-20原則,即20%的程式消耗了80%的執行時間,因而我們要改進效率,最主要是考慮改進那20%的程式碼。
7、提高迴圈語言的效率
在 C 語言中迴圈語句使用頻繁,提高迴圈體效率的基本辦法就是降低迴圈體的複雜性:
(1) 在多重迴圈中,應將最長的迴圈放在最內層,最短的迴圈放在最外層。這樣可以減少 CPU跨切迴圈的次數。如例 1-1 的.效率比 1-2 的效率要低:
[plain] view plain copy print?
for (j = 0; j < 30; j++)
{
for (i = 0; i < 10; i++)
{……}
} // 例子 1-1
for (i = 0; i < 10; i++)
{
for (j = 0; j < 30; j++)
{……}
} // 例子 2-2 程式部簡潔但效率高
8、提高 switch 語句的效率
switch 語句是 C 語言中常用的選擇語句, 在編譯時會產生if- else- if 巢狀程式碼,並按照順序進行比較,發現匹配時,就跳轉到滿足條件的語句執行。
當 switch 語句中的 case 標號很多時,為了減少比較的次數,可以把發生頻率相對高的條件放到第一位或者把整個 switch 語句轉化巢狀 switch 語句。把發生頻率高的 case 標號放在最外層的 switch 語句中,發生相對頻率相對低的 case 標號放在另外的 switch 語句中。如例 3 中,把發生率高的case 標號放在外層的 switch 語句中,把發生頻率低的放在預設的(default)內層 switch 語句中。
[plain] view plain copy print?
switch (表示式)
{
case 值1:
語句1: break;
case 值2:
語句2:break;
……
/*把發生頻率低的放在內層的switch語句中*/
default:
switch (表示式)
{
case 值n:
語句n: break;
case 值m:
語句m: break;
……
}
}
例子3 使用巢狀switch語句提高程式執行效率。
9、避免使用標準庫
使用 C語言標準庫可以加快開發進度,但由於標準庫需要設法處理使用者所有可能遇到的情況,所以很多標準庫程式碼很大。比如標準庫中的 sprintf函式非常大。這個龐大的程式碼中有很大一部分用於處理浮點數,如果程式中不需要格式化浮點數值( 如%f),程式設計人員就可以根據實際情況用少量的程式碼實現這個功能。
10、採用數學方法最佳化程式
數學是計算機之母,沒有數學的依據和基礎,就沒有計算機的發展,所以在編寫程式的時候,採用一些數學方法會對程式的執行效率有數量級的提高。有時候這個問題常常被大家忽略, 對於沒有經驗的程式設計師來說更是如此。例如:求 1~100 的和:
sum = 100*(100+1)/2;
數學公式: (a1 + an)*n/2
使用C語言的位操作可以減少除法和取模的運算。在計算機程式中資料的位是可以操作的最小資料單位,理論上可以用“位運算”來完成所有的運算和操作。因而,靈活的位操作可以有效地提高程式執行的效率。比如用用位操作區代替除法:比如:128 / 8 ->> 128 >> 3;
最佳化演算法和資料結構對提高程式碼的效率有很大的幫助。當然有時候時間效率和空間效率是對立的,此時應分析哪個更重要, 做出適當的折中。另外,在進行最佳化的時候不要片面的追求緊湊的程式碼,因為緊湊的程式碼並不能產生高效率的機器碼。
11、儲存器分配
由於成本限制,嵌入式系統儲存器容量有限。程式中所有的變數,包含的庫函式以及堆疊等都使用有限的記憶體:全域性變數在整個程式範圍內都有效。程式執行完後才會釋放;靜態變數的作用範圍也是整個程式,只有區域性變數中的動態變數在函式執行完後會釋放。因此, 在程式中應儘量使用區域性變數,提高記憶體使用效率。程式中堆的大小受限於所有全域性資料和棧空間都分配後的剩餘量,如果堆太小,程式不能夠在需要的時候分配記憶體。因此在使用 malloc 函式申請記憶體之後一定要用 free 函式進行釋放, 防止記憶體洩露。
12、選擇好的無限迴圈
在程式設計中,我們常常需要用到無限迴圈,常用的兩種方法是while (1) 和 for (;;)。這兩種方法效果完全一樣,但那一種更好呢?然我們看看它們編譯後的程式碼:
編譯前:
while (1);
編譯後:
mov eax,1
test eax,eax
je foo+23h
jmp foo+18h
編譯前:
for (;;);
編譯後:
jmp foo+23h
顯然,for (;;)指令少,不佔用暫存器,而且沒有判斷,跳轉,比while (1)好。
13、使用Memoization,以避免遞迴重複計算
考慮Fibonacci(斐波那契)問題,Fibonacci問題是可以透過簡單的遞迴方法來解決:
[plain] view plain copy print?
1. int fib ( n )
2. {
3. if ( n == 0 || n == 1 )
4. {
5. return 1;
6. }
7. else
8. {
9. return fib( n - 2 ) + fib ( n - 1 );
10. }
11. }
注:在這裡,我們考慮Fibonacci 系列從1開始,因此,該系列看起來:1,1,2,3,5,8,…
注意:從遞迴樹,我們計算fib(3)函式2次,fib(2)函式3次。這是相同函式的重複計算。如果n非常大,fib函式的效率會比較低。Memoization是一個簡單的技術,可以被用在遞迴,加強計算速度。fibonacci 函式Memoization的程式碼如下:
[plain] view plain copy print?
1. int calc_fib ( int n )
2. {
3. int val[ n ] , i;
4. for ( i = 0; i <=n; i++ )
5. {
6. val[ i ] = -1; // Value of the first n + 1 terms of the fibonacci terms set to -1
7. }
8. val[ 0 ] = 1; // Value of fib ( 0 ) is set to 1
9. val[ 1 ] = 1; // Value of fib ( 1 ) is set to 1
10. return fib( n , val );
11. }
12.
13. int fib( int n , int* value )
14. {
15. if ( value[ n ] != -1 )
16. {
17. return value[ n ]; // Using memoization
18. }
19. else
20. {
21. value[ n ] = fib( n - 2 , value ) + fib ( n - 1 , value ); // Computing the fibonacci term
22. }
23. return value[ n ]; // Returning the value
24. }
除了程式設計上的技巧外,為提高系統的執行效率,我們通常也需要最大可能地利用各種硬體裝置自身的特點來減小其運轉開銷,例如減小中斷次數,利用DMA傳輸方式等。