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