C說話中關於輪回構造優化的一些入門級辦法簡介。本站提示廣大學習愛好者:(C說話中關於輪回構造優化的一些入門級辦法簡介)文章只能為提供參考,不一定能成為您想要的結果。以下是C說話中關於輪回構造優化的一些入門級辦法簡介正文
一.代碼挪動
將在輪回外面屢次盤算,然則成果不會轉變的盤算,移到輪回裡面去。
例子:
優化前:
void lower1(char *s){ int i; for(i=0;i<strlen(s);++i) if(s[i]>='A'&&s[i]<='Z') s[i]-=('A'-'a'); } 優化後: void lower2(char *s){ int i; int len=strlen(s); for(int i=0;i<len;++i) if(s[i]>='A'&&s[i]<='Z') s[i]-=('A'-'a'); }
優化前的版本,因為每次輪回都要挪用strlen盤算s的長度,現實上的龐雜度成了O(n2)了,而優化後的版本只需盤算一次s的長度,是以機能上比優化前版本要好。
二.削減函數挪用
例子:
優化前:
void sum1(vec_ptr v,data_t *dest){ int i; int len=vec_length(v); *dest=0; for(i=0;i<len;++i){ data_t val; get_vec_element(v,i,&val); *dest+=val; } }
優化後:
data_t get_vec_start(vec_ptr v){ return v->data; } void sum2(vec_ptr v,data_t *dest){ int i; int len=vec_length(v); data_t *data=get_vec_start(v); *dest=0; for(i=0;i<len;++i) *dest+=data[i]; }
優化前的版本在每次輪回中都要挪用一次get_vec_element取得響應的項,而優化後的版本只需在輪回外挪用一次get_vec_start取得開端的內存地址,輪回內直接拜訪內存,無需挪用函數。
三.削減內存拜訪
例子:
優化前:
void sum2(vec_ptr v,data_t *dest){ int i; int len=vec_length(v); data_t *data=get_vec_start(v); *dest=0; for(i=0;i<len;++i) *dest+=data[i]; }
優化後:
void sum3(vec_ptr v,data_t *dest){ int i; int len=vec_length(v); data_t *data=get_vec_start(v); data_t acc=0; for(i=0;i<len;++i) acc+=data[i]; *dest=acc; }
優化前的版本每次迭代都要從dest讀出值再加上data[i],再將成果寫回dest。如許的讀寫很糟蹋,是以每次迭代開端從dest讀出的值就是前次迭代寫回dest的指。優化後的版本經由過程參加acc暫時變量,它輪回中積累盤算出的成果,輪回停止後再寫回。
這裡給出兩個版原形應的匯編成果便可以很清晰看出差別:
優化前:
優化前的版本每次迭代都要從dest讀出值再加上data[i],再將成果寫回dest。如許的讀寫很糟蹋,是以每次迭代開端從dest讀出的值就是前次迭代寫回dest的指。優化後的版本經由過程參加acc暫時變量,它輪回中積累盤算出的成果,輪回停止後再寫回。
第二行和第四行分離對dest停止了讀寫。
優化後:
從匯編成果可以看出編譯器將acc直接放在了存放器裡,輪回中無需對內存停止讀寫。
四.輪回睜開
輪回睜開可以削減輪回的次數,對法式的機能帶了兩方面的進步。一是削減了對輪回沒有直接進獻的盤算,好比輪回計數變量的盤算,分支跳轉指令的履行等。二是供給了進一步應用機械特征停止的優化的機遇。
例子:
優化前的代碼見前一篇博客裡的sum3.
優化後:
void sum4(vec_ptr v,data_t *dest){ int i; int len=vec_length(v); int limit=len-3; data_t *data=get_vec_start(v); data_t acc=0; for(i=0;i<limit;i+=4){ acc=acc+data[i]+data[i+1]; acc=acc+data[i+2]+data[i+3]; } for(;i<len;++i) acc+=data[i]; *dest=acc; }
經由過程輪回睜開,每次迭代將累加4個元素,削減了輪回次數,從而削減了總的履行時光(零丁應用這類優化辦法,對浮點數累乘簡直沒有進步,然則整數累乘得益於編譯器的重聯系關系代碼變更會有年夜幅度進步)。
這類優化可以直接應用編譯器完成,將優化level設定到較高,編譯器會主動停止輪回睜開。應用gcc,可以顯式應用-funroll-loops選項。
五.進步並行性
古代處置器年夜多采取了流水線、超標量等技巧,可以完成指令級並行。我們可以應用這個特征對代碼做進一步的優化。
2.1應用多個積累變量
優化代碼示例
void sum5(vec_ptr v,data_t *dest){ int i; int len=vec_length(v); int limit=len-1; data_t *data=get_vec_start(v); data_t acc0=0; data_t acc1=0; for(i=0;i<limit;i+=2){ acc0+=data[i]; acc1+=data[i+1]; } for(;i<len;++i) acc0+=data[i]; *dest=acc0+acc1; }
這裡同時應用了輪回睜開和應用多個累加變量,一方面削減了輪回次數,另外一方面指令級並行的特征使得每次迭代的兩次加法可以並行履行。基於這兩點可以明顯削減法式履行的時光。經由過程增長睜開的次數和累加變量的個數,可以進一步進步法式的機能,直到機械指令履行的吞吐量的極限。
2.2重聯合變換
除應用多個積累變量顯式應用機械的指令級並行特征外,還可以對運算從新聯合變換,打破次序相干性來享用指令級並行帶來的利益。
在sum4中,acc=acc+data[i]+data[i+1]的聯合次序是acc=(acc+data[i])+data[i+1];
我們將之釀成acc=acc+(data[i]+data[i+1]);
代碼以下:
void sum6(vec_ptr v,data_t *dest){ int i; int len=vec_length(v); int limit=len-3; data_t *data=get_vec_start(v); data_t acc=0; for(i=0;i<limit;i+=4){ acc=acc+(data[i]+data[i+1]); acc=acc+(data[i+2]+data[i+3]); } for(;i<len;++i) acc+=data[i]; *dest=acc; }
進一步增長輪回睜開的次數,可以進一步進步法式機能,終究也能夠到達機械指令履行的吞吐量的極限。(在輪回展現提到的整數乘法的機能進步就在於編譯器隱式采用了這類變換,然則因為浮點數不具有聯合性,所以編譯器沒有采取,然則法式員在包管法式成果准確性的情形下,可以顯式應用這一點)。