優化程式效能
優化程式效能
編寫高效程式需要做到如下幾點
1.選擇適當的演算法和資料結構。
2.編寫出編譯器能夠有效優化以轉換成高效可執行程式碼的原始碼(理解編譯器的能力和侷限性尤為重要)。
3.針對運算量特別大的計算,將一個任務分成多個部分,並行的去計算。
程式示例
typedef long data_t typedef struct { long len; data_t *date }ver_rec,*ver_ptr; ver_ptr new(long len) { ver_ptr result = (vec_ptr)malloc(sizeof(ver_rec)); data_t *data = NULL; if (!result) { return NULL; } result->len = len; if(len > 0) { data = (data_t *)calloc(len, sizeof(data_t)); if(!data) { free((void *)result); return NULL; } } result->data = data; return result; } int get_vec_element(ver_ptr v, long index, data_t *dest) { if(index < 0 || index >=v->len) return 0 ; *dest = v->data[index]; return 1; } long ver_length(vec_ptr v) { return v->len; } void combine1(vec_ptr v, data_t *dest) { long i; *dest = 0; for(int i =0; i< vec_length(v); i++) { data_t val; get_vec_element(v,i,&val); *dest = *desc + val; } }
上面程式碼主要是給出生成向量、訪問元素、計算長度的一些方法。我們通過將一個向量中所有的元素合併成一個值得例子,來說明如果做程式優化。
消除迴圈的低效率
通過combine1函式我們可以看到,迴圈每個元素時,都會呼叫vec_length()方法,我們知道向量的長度不會隨著迴圈的進行而改變,我們只需要計算一次向量的長度。
void combine2(vec_ptr v, data_t *dest) { long i; long length = vec_length(v); *dest = 0; for(int i =0; i< length; i++) { data_t val; get_vec_element(v,i,&val); *dest = *desc + val; } }
這種優化稱為程式碼移動(code motion)。執行多次,但不會改變的計算,一般用程式碼移動來優化
減少過程呼叫
太多的過程呼叫會帶來效能的開銷,從combine我們可以看到,每次迴圈都會呼叫get_vec_element方法,我們可以用下面的方法來優化
void combine3(vec_ptr v, data_t *dest) { long i; long length = vec_length(v); data_t *data = get_vec_start(v); *dest = 0; for(int i =0; i< length; i++) { *dest = *desc + data[i]; } }
消除不必要的記憶體引用
我們看到每次迴圈迭代時,累積變數的值都要從記憶體中讀出在寫入到記憶體中,每次都要訪問兩次*dest。我們可以建立一個臨時變數,每次迴圈累積的值都用它來儲存,迴圈結束時,再把它複製到記憶體中。
void combine4(vec_ptr v, data_t *dest) { long i; long length = vec_length(v); data_t *data = get_vec_start(v); data_t acc = 0; *dest = 0; for(int i =0; i< length; i++) { acc = acc + data[i]; } *dest = acc; }
通過上面的優化,我們將每次迭代的記憶體操作從兩次讀和一次寫減少到只需要一次讀
迴圈展開
迴圈展開是一種程式變換,通過增加每次迭代程式計算的數量,減少迴圈的迭代次數。迴圈展開能夠從兩個方面改程序序的效能
1.減少了不直接有助於程式結果的操作的數量,列如迴圈索引計算和條件分支。
2.提供了一些方法,可以進一步變化程式碼,減少整個計算中關鍵路徑上的運算元量。
void combine5(vec_ptr v, data_t *dest) { long i; long length = vec_length(v); long limit = length - 1; data_t *data = get_vec_start(v); data_t acc = 0; for(int i =0; i< limit; i+=2) { acc = acc + data[i] + data[i+1]; } for(; i < length; i++) { acc = acc + data[i] } *dest = acc; }
提高並行性
多個累積變數
對於一個可結合和可變換的合併運算來說,比如說整數加法或乘法,我們可以通過將一組合並分割成兩個或更多部分。
void combine6(vec_ptr v, data_t *dest) { long i; long length = vec_length(v); long limit = length - 1; data_t *data = get_vec_start(v); data_t acc0 = 0; data_t acc1 = 0; for(int i =0; i< limit; i+=2) { acc0 = acc0 + data[i]; acc1 = acc1 + data[i+1]; } for(; i < length; i++) { acc0 = acc0 + data[i] } *dest = acc0 + acc1; }
重新結合變換
void combine7(vec_ptr v, data_t *dest) { long i; long length = vec_length(v); long limit = length - 1; data_t *data = get_vec_start(v); data_t acc = 0; for(int i =0; i< limit; i+=2) { acc = acc + (data[i] + data[i+1]); } for(; i < length; i++) { acc = acc + data[i] } *dest = acc; }
一些限制因素
暫存器溢位
如果我們的並行p超過了暫存器的數量,那麼編譯器就會溢位,將某些臨時值存放到記憶體中,通常是在執行時堆疊上分配空間。
瞭解更多Android知識,或者獲取相關資料請加入Android技術開發交流2群:935654177。本群可免費獲取Gradle,RxJava,小程式,Hybrid,移動架構,NDK,React Native,效能優化等技術教程!