嵌入式代碼優化是一個復雜的過程,它不僅取決于代碼本身,還取決于目標硬件平臺、編譯器以及優化的目標(例如速度、內存使用、功耗等)。
不過,有一些通用的技巧可以在編寫嵌入式代碼時考慮到:
使用查表法
在內存空間較為充足的情況下,有時候可以犧牲一些空間來換取程序的運行速度。查表法就是 以空間換取時間 的典型例子。
比如:編寫程序統計一個4bit(0x0~0xF)數據中1的個數。
使用查表法:
static?int?table[16]?=?{0,?1,?1,?2,?1,?2,?2,?3,?1,?2,?2,?3,?2,?3,?3,?4}; int?get_digits_1_num(unsigned?char?data) { ?int?cnt?=?0; ?unsigned?char?temp?=?data?&?0xf;?? ? ?cnt?=?table[temp]; ? ?return?cnt; }
優于:
int?get_digits_1_num(unsigned?char?data) { ?int?cnt?=?0; ?unsigned?char?temp?=?data?&?0xf;?? ? ?for?(int?i?=?0;?i?4;?i++) ?{ ??if?(temp?&?0x01) ??{ ???cnt++; ??} ??temp?>>=?1; ?} ? ?return?cnt; }
查表法把0x0~0xF中的所有數據中每個數據的1的個數都記錄下來,存放到一個表中。這樣一來,數據與數據中1的個數就建立起了一一對應關系,就可以通過數組索引來獲取得到結果。常規法使用for循環的方式來實現,缺點是占用了不少處理器的時間。
特別地,對于越復雜地運算,查表法較常規法更有優勢。另一方面,查表法的代碼往往比常規法要簡潔些。
使用柔性數組
C99中,結構體中的最后一個元素允許是未知大小的數組,這就叫作 柔性數組 。
柔性數組的特點:
結構體中柔性數組成員前面必須至少有一個其他成員。
sizeof返回的這種結構大小不包括柔性數組的內存。
包含柔性數組成員的結構用malloc()函數進行內存的動態分配。
在C99標準環境中,使用柔性數組:
typedef?struct?_protocol_format { ????uint16_t?head;???? ????uint8_t?id; ????uint8_t?type; ????uint8_t?length; ????uint8_t?value[]; }protocol_format_t;
優于使用指針:
typedef?struct?_protocol_format { ????uint16_t?head;???? ????uint8_t?id; ????uint8_t?type; ????uint8_t?length; ????uint8_t?*value; }protocol_format_t;
柔性數組的方式結構體占用較指針的方式少。
柔性數組的方式相對與指針的方式更為簡潔,給結構體申請空間的同時也給柔性數組申請空間,柔性數組的方式只需要申請一次空間,是一塊連續內存,連續的內存有益于提高訪問速度;而指針的方式,除了給結構體申請空間之外,還得給結構體里的指針成員申請空間。
使用指針的方式寫代碼會比柔性數組的方式會繁瑣一些,特別地,如果在釋放內存的時候把順序弄反了,則結構體里的指針成員所指向的內存就釋放不掉,會造成內存泄露。
使用位操作
1、使用位域
有些數據在存儲時并不需要占用一個完整的字節,只需要占用一個或幾個二進制位即可。
比如:管理一些標志位。
使用位域:
struct?{ ????unsigned?char?flag1:1; ????unsigned?char?flag2:1; ????unsigned?char?flag3:1; ????unsigned?char?flag4:1; ????unsigned?char?flag5:1; ????unsigned?char?flag6:1; ????unsigned?char?flag7:1; ????unsigned?char?flag8:1; }?flags;
優于:
struct?{ ????unsigned?char?flag1; ????unsigned?char?flag2; ????unsigned?char?flag3; ????unsigned?char?flag4; ????unsigned?char?flag5; ????unsigned?char?flag6; ????unsigned?char?flag7; ????unsigned?char?flag8; }?flags;
2、使用位操作代替除法和乘法
使用位操作:
uint32_t?val?=?1024; uint32_t?doubled?=?val?<1;? uint32_t?halved?=?val?>>?1;?
優于:
uint32_t?val?=?1024; uint32_t?doubled?=?val?*?2 uint32_t?halved?=?val?/?2
循環展開
有時候,可以犧牲一點代碼的簡潔度、減少循環控制語句的執行頻率以提高性能。
無依賴的循環展開:
process(array[0]); process(array[1]); process(array[2]); process(array[3]);
優于:
for?(int?i?=?0;?i?4;?i++)? { ????process(array[i]); }
有依賴的循環展開:
long?calc_sum(int?*a,?int?*b) { ?long?sum0?=?0; ?long?sum1?=?0; ?long?sum2?=?0; ?long?sum3?=?0; ? ?for?(int?i?=?0;?i?250;?i?+=?4) ?{ ??sum0?+=?arr0[i?+?0]?*?arr1[i?+?0]; ??sum1?+=?arr0[i?+?1]?*?arr1[i?+?1]; ??sum2?+=?arr0[i?+?2]?*?arr1[i?+?2]; ??sum3?+=?arr0[i?+?3]?*?arr1[i?+?3]; ?} ? ?return?(sum0?+?sum1?+?sum2?+?sum3); }
優于:
long?calc_sum(int?*a,?int?*b) { ?long?sum?=?0; ? ?for?(int?i?=?0;?i?1000;?i?++) ?{ ??sum?+=?arr0[i]?*?arr1[i]; ?} ? ?return?sum; }
盡可能把長的有依賴的代碼鏈分解成幾個可以在流水線執行單元中并行執行的沒有依賴的代碼鏈,提高流水線的連續性。通常4次展開為最佳方式。
使用內聯函數
使用內聯函數替換重復的短代碼,一方面,可以避免函數的回調,加速了程序的執行,利用指令緩存,增強局部訪問性;另一方面,可以方便代碼管理。
如:翻轉led的操作。
static?inline?void?toggle_led(uint8_t?pin) { ????PORT?^=?1?<使用合適的數據類型
首先使用合適的數據類型。
比如幾種數據類型都滿足需求的情況下,更小的可能并不是最合適的。
比如:素組索引的變量類型。
數組索引應盡量采用int類型。
int?i; for?(i?=?0;?i?優于:
char?i; for?(i?=?0;?i?定義為char類型,一般會有溢出的風險,因此編譯器需要使用多余的指令判斷是否溢出;而使用int類型,一般編譯器默認不會超過這么大的循環次數,從而減少了不必要的指令。
其它情況下,在滿足數據范圍的情況下,能夠使用字符型(char)定義的變量,就不要使用整型(int)變量來定義;能夠使用整型變量定義的變量就不要用長整型(long int),能不使用浮點型(float)變量就不要使用浮點型變量。
多重循環優化
長循環在最內層:
for?(col?=?0;?col?5;?col++) { ?for?(row?=?0;?row?100;?row++) ?{ ??sum?=?sum?+?a[row][col]; ?} }優于長循環在最外層:
for?(row?=?0;?row?100;?row++) { ?for(col=0;?col?5;?col++?) ?{ ??sum?=?sum?+?a[row][col]; ?} }在多重循環中,應當將最長的循環放在最內層, 最短的循環放在最外層,以減少 CPU 跨切循環層的次數。
盡早退出循環
通常,循環并不需要全部都執行。
例如,如果我們在從數組中查找一個特殊的值,一經找到,我們應該盡可能早的斷開循環。例如:如下循環從10000個整數中查找是否存在-99。
char?found?=?FALSE; for(i?=?0;?i?10000;?i++) { ????if?(list[i]?==?-99) ????{ ????????found?=?TRUE; ????} } ? if?(found)? { ????printf("Yes,?there?is?a?-99.?Hooray! "); }這段代碼無論我們是否查找得到,循環都會全部執行完。更好的方法是一旦找到我們查找的數字就終止繼續查詢。把程序修改為:
found?=?FALSE; for?(i?=?0;?i?10000;?i++) { ????if?(list[i]?==?-99) ????{ ????????found?=?TRUE; ????????break; ????} } ? if?(found)? { ????printf("Yes,?there?is?a?-99.?Hooray! "); }假如待查數據位于第23個位置上,程序便會執行23次,從而節省9977次循環。
結構體內存對齊
必要時,手動對齊結構體的內存排列。
比如:
typedef?struct?test_struct { ?char?a;?? ?short?b;????? ?char?c;????? ?int?d; ?char?e; }test_struct;該結構體在32bit環境中,該結構體所占的字節數為16。
可以手動調整各成員的位置來進行空白字節填充以達到對齊的效果。如:
typedef?struct?test_struct { ?char?a;?? ?char?c;? ?short?b;????????? ?int?d; ?char?e; }test_struct;則結構體變量test_s所占的字節數變為12字節,比原來的16字節省下了4個字節。
優化中斷處理
確保中斷處理快速且盡可能短。
//?中斷例程應該盡量簡短 void?ISR()? { ????flag?=?true; }利用硬件特性
使用硬件模塊或特有指令來減輕CPU負擔。
//?比如,直接使用DMA傳輸而不經由CPU DMA_Config(&src,?&dest,?length); DMA_Start();以上就是本次的分享。一些優化可能會增加代碼的復雜性或降低可讀性或其它方面的影響,因此在決定應用優化時,需權衡不同方面的影響。
審核編輯:黃飛
?
評論
查看更多