同樣的事情,使用不一樣的方法去完成,雖然最終的結果一樣,但是完成的效率往往不一樣。假如你離家一千公里路程,過年要回家過春節,你可以走路回家,可以騎自行車回家,可以騎摩托車回家,可以坐汽車回家,可以坐火車回家,當然也可以坐飛機回家,雖然最終目的都是到達一千公里的家鄉,但是乘坐不同的交通工具,回家的時間各異。在程序中,這些不同的“交通工具”我們稱之為算法。
代碼的運算速度取決于以下幾個方面:
(1)處理器的主頻和設計架構;
(2)處理器的總線帶寬;
(3)程序代碼的設計編寫;
(4)程序中所使用算法本身的復雜度,比如MPEG比JPEG復雜,JPEG比BMP圖片的編碼復雜。
比如在一個圖像轉換的項目中,需要將RGB格式的彩色圖像先轉換成黑白圖像。圖像轉換的公式如下:
Y = 0.299 * R + 0.587 * G + 0.114 * B;
圖像尺寸640*480*24 bits,RGB圖像已經按照RGBRGB順序排列的格式,放在內存里面了。
例如,將這個噴火的戰斗機引擎,轉換為右邊的黑白圖片。
圖片輸入和輸出的定義如下:
#define XSIZE (640) #define YSIZE (480) #define IMGSIZE XSIZE*YSIZE typedef struct rgb { uint8_t r; uint8_t g; uint8_t b; }RGB; RGB in[IMGSIZE]; /* 未轉換的圖片數據 */ uint8_t out[IMGSIZE]; /* 轉換后的圖片數據 */
優化原則:
圖像是一個二維數組,我用一個一維數組來存儲。編譯器處理一維數組的效率要高于二維數組。
第一個程序:
void convert_rgb_image(void) { int i = 0; for(i = 0; i < IMGSIZE; i++) { uint8_t r = in[i].r; uint8_t g = in[i].g; uint8_t b = in[i].b; double temp_out = 0.299 * r + 0.587 * g + 0.114 * b; out[i] = temp_out; } }
分別用VC6.0和交叉編譯工具,生成2個版本,分別在PC和嵌入式開發板上面運行。
A、在PC上,由于存在硬件浮點處理器,CPU頻率也夠高,運行時間為20秒。
B、在嵌入式開發板 ,主頻時鐘比較低,也沒有浮點處理器,浮點操作被編譯器分解成了整數運算,運行時間為120秒左右。
第一次優化
優化原則:
去掉浮點數運算。
在公式Y = 0.299 * R + 0.587 * G + 0.114 * B中,由于RGB的取值范圍都是0~255,只是系數都是浮點數,將RGB的系數轉換為:
R的系數:0.299 = 299 / 1000
G的系數:0.587 = 587 / 1000
B的系數:0.114 = 114 / 1000
所以圖片轉換公式可表示成:Y = (299 * R + 587 * G + 114 * B)/ 1000
即轉換圖片的程序變為:
void convert_rgb_image(void) { int i = 0; for(i = 0; i < IMGSIZE; i++) { uint8_t r = in[i].r; uint8_t g = in[i].g; uint8_t b = in[i].b; double temp_out = (299 * r + 587 * g + 114 * b) / 1000; out[i] = temp_out; } }
再次編譯生成兩個平臺的應用程序運行,發現:
A、在PC上運行的時間為2秒
B、在嵌入式開發板上運行的時間為45秒
第二次優化
優化原則:
處理器在進行除法運算時,處理速度比較慢,去除除法操作
將公式Y = (299 * R + 587 * G + 114 * B)/ 1000的RGB的系數優化如下:
R的系數:0.299 = 299 / 1000 = 1224 / 4096
G的系數:0.587 = 587 / 1000 = 2404 / 4096
B的系數:0.114 = 114 / 1000 = 467 / 4096
由于4096是2的倍數,除法可用效率更高的移位操作進行優化,所以圖片轉換公式為:
Y = (1224 * R + 2404 * G + 467 * G) >> 12
所以圖片轉換程序為:
void convert_rgb_image(void) { int i = 0; for(i = 0; i < IMGSIZE; i++) { int r = 1224 * in[i].r; int g = 2404 * in[i].g; int b = 467 * in[i].b; int temp_out = (r + g + b) >> 12; out[i] = temp_out; } }
再次編譯運行,發現在嵌入式開發板上運行時間為30秒。
第三次優化
優化原則:
由于每一次轉換的RGB系數都要經過計算得到,減少各個系數的計算次數。
優化代碼如下:
#define RGB_SIZE (256) int R[RGB_SIZE]; int G[RGB_SIZE]; int B[RGB_SIZE]; void rgb_table_init(void) { int i = 0; for(i = 0; i < RGB_SIZE; i++) { R[i] = 1224 * i; R[i] = R[i] >> 12; G[i] = 2404 * i; G[i] = G[i] >> 12; B[i] = 467 * i; B[i] = B[i] >> 12; } } void convert_rgb_image(void) { int i = 0; for(i = 0; i < IMGSIZE; i++) { int r = R[in[i].r]; int g = G[in[i].g]; int b = B[in[i].b]; int temp_out = r + g + b; out[i] = temp_out; } }
再次編譯運行,發現在嵌入式開發板上運行時間為2秒。
第四次優化
優化原則:
32位的嵌入式CPU,都至少有2個算術邏輯單元(ALU),讓2個ALU一起運行
優化代碼如下:
#define RGB_SIZE (256) int R[RGB_SIZE]; int G[RGB_SIZE]; int B[RGB_SIZE]; void rgb_table_init(void) { int i = 0; for(i = 0; i < RGB_SIZE; i++) { R[i] = 1224 * i; R[i] = R[i] >> 12; G[i] = 2404 * i; G[i] = G[i] >> 12; B[i] = 467 * i; B[i] = B[i] >> 12; } } void convert_rgb_image(void) { int i = 0; for(i=0; i < IMGSIZE; i += 2) { /* 給第一個算術邏輯單元執行 */ int r0 = R[in[i].r]; int g0 = G[in[i].g]; int b0 = B[in[i].b]; int temp_out_0 = r0 + g0 + b0; out[i] = temp_out_0; /* 給第二個算術邏輯單元執行 */ int r1 = R[in[i+1].r]; int g1 = G[in[i+1].g]; int b1 = B[in[i+1].b]; int temp_out_1 = r1 + g1 + b1; out[i+1] = temp_out_1; /* 如果有更多算術邏輯單元,可以類似的處理代碼 */ } }
再次編譯運行,發現在嵌入式開發板上運行時間為1秒。
第五次優化
優化原則:
由于各個數據類型大小不一樣,處理速度也不一樣,因此可以對數據類型優化
優化代碼如下:
#define RGB_SIZE (256) uint16_t R[RGB_SIZE]; uint16_t G[RGB_SIZE]; uint16_t B[RGB_SIZE]; void rgb_table_init(void) { uint8_t i = 0; for(i = 0; i <= RGB_SIZE; i++) { R[i] = 1224 * i; R[i] = R[i] >> 12; G[i] = 2404 * i; G[i] = G[i] >> 12; B[i] = 467 * i; B[i] = B[i] >> 12; } } inline void convert_rgb_image(void) { uint32_t i = 0; for(i=0; i < IMGSIZE; i += 2) { /* 給第一個算術邏輯單元執行 */ uint16_t r0 = R[in[i].r]; uint16_t g0 = G[in[i].g]; uint16_t b0 = B[in[i].b]; uint32_t temp_out_0 = r0 + g0 + b0; out[i] = temp_out_0; /* 給第二個算術邏輯單元執行 */ uint16_t r1 = R[in[i+1].r]; uint16_t g1 = G[in[i+1].g]; uint16_t b1 = B[in[i+1].b]; uint32_t temp_out_1 = r1 + g1 + b1; out[i+1] = temp_out_1; } }
將函數聲明為inline,這樣編譯器就會將其嵌入到母函數中,可以減少CPU調用子函數所產生的開銷。
再次編譯運行,發現在嵌入式開發板上運行時間為0.5秒。
后續可優化的方向:
(1)將RGB查表的數據放入CPU的高速緩沖存儲器(Cache)中,從而提升程序運行時的加載速度。
(2)代碼使用匯編語言進行編寫
說明:本文來源于網絡,我僅僅是對文章進行了一定的整理,刪繁就簡,如有侵權,請及時聯系我刪除!