国产精品久久久aaaa,日日干夜夜操天天插,亚洲乱熟女香蕉一区二区三区少妇,99精品国产高清一区二区三区,国产成人精品一区二区色戒,久久久国产精品成人免费,亚洲精品毛片久久久久,99久久婷婷国产综合精品电影,国产一区二区三区任你鲁

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

9個提高代碼運行效率的小技巧

Q4MP_gh_c472c21 ? 來源:嵌入式與Linux那些事 ? 作者:嵌入式與Linux那些 ? 2021-01-21 09:59 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

我們寫程序的目的就是使它在任何情況下都可以穩定工作。一個運行的很快但是結果錯誤的程序并沒有任何用處。在程序開發和優化的過程中,我們必須考慮代碼使用的方式,以及影響它的關鍵因素。通常,我們必須在程序的簡潔性與它的運行速度之間做出權衡。今天我們就來聊一聊如何優化程序的性能。

1. 減小程序計算量

1.1 示例代碼

1.2 分析代碼

1.3 改進代碼

2. 提取代碼中的公共部分

2.1 示例代碼

2.2 分析代碼

2.3 改進代碼

3. 消除循環中低效代碼

3.1 示例代碼

3.2 分析代碼

3.3 改進代碼

4. 消除不必要的內存引用

4.1 示例代碼

4.2 分析代碼

4.3 改進代碼

5. 減小不必要的調用

5.1 示例代碼

5.2 分析代碼

5.3 改進代碼

6. 循環展開

6.1 示例代碼

6.2 分析代碼

6.3 改進代碼

7. 累計變量,多路并行

7.1 示例代碼

7.2 分析代碼

7.3 改進代碼

8. 重新結合變換

8.1 示例代碼

8.2 分析代碼

8.3 改進代碼

9 條件傳送風格的代碼

9.1 示例代碼

9.2 分析代碼

9.3 改進代碼

10. 總結

1. 減小程序計算量

1.1 示例代碼

for (i = 0; i 《 n; i++) { int ni = n*i; for (j = 0; j 《 n; j++) a[ni + j] = b[j]; }

1.2 分析代碼

代碼如上所示,外循環每執行一次,我們要進行一次乘法計算。i = 0,ni = 0;i = 1,ni = n;i = 2,ni = 2n。因此,我們可以把乘法換成加法,以n為步長,這樣就減小了外循環的代碼量。

1.3 改進代碼

int ni = 0; for (i = 0; i 《 n; i++) { for (j = 0; j 《 n; j++) a[ni + j] = b[j]; ni += n; //乘法改加法 }

計算機中加法指令要比乘法指令慢得多。

2. 提取代碼中的公共部分

2.1 示例代碼

想象一下,我們有一個圖像,我們把圖像表示為二維數組,數組元素代表像素點。我們想要得到給定像素的東、南、西、北四個鄰居的總和。并求他們的平均值或他們的和。代碼如下所示。

up = val[(i-1)*n + j ]; down = val[(i+1)*n + j ]; left = val[i*n + j-1]; right = val[i*n + j+1]; sum = up + down + left + right;

2.2 分析代碼

將以上代碼編譯后得到匯編代碼如下所示,注意下3,4,5行,有三個乘以n的乘法運算。我們把上面的up和down展開后會發現四格表達式中都有i*n + j。因此,可以提取出公共部分,再通過加減運算分別得出up、down等的值。

leaq 1(%rsi), %rax # i+1 leaq -1(%rsi), %r8 # i-1 imulq %rcx, %rsi # i*n imulq %rcx, %rax # (i+1)*n imulq %rcx, %r8 # (i-1)*n addq %rdx, %rsi # i*n+j addq %rdx, %rax # (i+1)*n+j addq %rdx, %r8 # (i-1)*n+j

2.3 改進代碼

long inj = i*n + j; up = val[inj - n]; down = val[inj + n]; left = val[inj - 1]; right = val[inj + 1]; sum = up + down + left + right;

改進后的代碼的匯編如下所示。編譯后只有一個乘法。減少了6個時鐘周期(一個乘法周期大約為3個時鐘周期)。

imulq %rcx, %rsi # i*n addq %rdx, %rsi # i*n+j movq %rsi, %rax # i*n+j subq %rcx, %rax # i*n+j-n leaq (%rsi,%rcx), %rcx # i*n+j+n 。..

對于GCC編譯器來說,編譯器可以根據不同的優化等級,有不同的優化方式,會自動完成以上的優化操作。下面我們介紹下,那些必須是我們要手動優化的。

3. 消除循環中低效代碼

3.1 示例代碼

程序看起來沒什么問題,一個很平常的大小寫轉換的代碼,但是為什么隨著字符串輸入長度的變長,代碼的執行時間會呈指數式增長呢?

void lower1(char *s) { size_t i; for (i = 0; i 《 strlen(s); i++) if (s[i] 》= ‘A’ && s[i] 《= ‘Z’) s[i] -= (‘A’ - ‘a’); }

3.2 分析代碼

那么我們就測試下代碼,輸入一系列字符串。

7b754a5c-57dd-11eb-8b86-12bb97331649.png

lower1代碼性能測試

當輸入字符串長度低于100000時,程序運行時間差別不大。但是,隨著字符串長度的增加,程序的運行時間呈指數時增長。

我們把代碼轉換成goto形式看下。

void lower1(char *s) { size_t i = 0; if (i 》= strlen(s)) goto done; loop: if (s[i] 》= ‘A’ && s[i] 《= ‘Z’) s[i] -= (‘A’ - ‘a’); i++; if (i 《 strlen(s)) goto loop; done: }

以上代碼分為初始化(第3行),測試(第4行),更新(第9,10行)三部分。初始化只會執行一次。但是測試和更新每次都會執行。每進行一次循環,都會對strlen調用一次。

下面我們看下strlen函數的源碼是如何計算字符串長度的。

size_t strlen(const char *s) { size_t length = 0; while (*s != ‘’) { s++; length++; } return length; }

strlen函數計算字符串長度的原理為:遍歷字符串,直到遇到‘’才會停止。因此,strlen函數的時間復雜度為O(N)。lower1中,對于長度為N的字符串來說,strlen 的調用次數為N,N-1,N-2 。.. 1。對于一個線性時間的函數調用N次,其時間復雜度接近于O(N2)。

3.3 改進代碼

對于循環中出現的這種冗余調用,我們可以將其移動到循環外。將計算結果用于循環中。改進后的代碼如下所示。

void lower2(char *s) { size_t i; size_t len = strlen(s); for (i = 0; i 《 len; i++) if (s[i] 》= ‘A’ && s[i] 《= ‘Z’) s[i] -= (‘A’ - ‘a’); }

將兩個函數對比下,如下圖所示。lower2函數的執行時間得到明顯提升。

7b9dce3c-57dd-11eb-8b86-12bb97331649.png

lower1和lower2代碼效率

4. 消除不必要的內存引用

4.1 示例代碼

以下代碼作用為,計算a數組中每一行所有元素的和存在b[i]中。

void sum_rows1(double *a, double *b, long n) { long i, j; for (i = 0; i 《 n; i++) { b[i] = 0; for (j = 0; j 《 n; j++) b[i] += a[i*n + j]; } }

4.2 分析代碼

匯編代碼如下所示。

# sum_rows1 inner loop .L4: movsd (%rsi,%rax,8), %xmm0 # 從內存中讀取某個值放到%xmm0 addsd (%rdi), %xmm0 # %xmm0 加上某個值 movsd %xmm0, (%rsi,%rax,8) # %xmm0 的值寫回內存,其實就是b[i] addq $8, %rdi cmpq %rcx, %rdi jne .L4

這意味著每次循環都需要從內存中讀取b[i],然后再把b[i]寫回內存 。b[i] += b[i] + a[i*n + j]; 其實每次循環開始的時候,b[i]就是上一次的值。為什么每次都要從內存中讀取出來再寫回呢?

4.3 改進代碼

/* Sum rows is of n X n matrix a and store in vector b */ void sum_rows2(double *a, double *b, long n) { long i, j; for (i = 0; i 《 n; i++) { double val = 0; for (j = 0; j 《 n; j++) val += a[i*n + j]; b[i] = val; } }

匯編如下所示。

# sum_rows2 inner loop .L10: addsd (%rdi), %xmm0 # FP load + add addq $8, %rdi cmpq %rax, %rdi jne .L10

改進后的代碼引入了臨時變量來保存中間結果,只有在最后的值計算出來時,才將結果存放到數組或全局變量中。

5. 減小不必要的調用

5.1 示例代碼

為了方便舉例,我們定義一個包含數組和數組長度的結構體,主要是為了防止數組訪問越界,data_t可以是int,long等類型。具體如下所示。

typedef struct{ size_t len; data_t *data; } vec;

7bdcb444-57dd-11eb-8b86-12bb97331649.png

vec向量示意圖

get_vec_element函數的作用是遍歷data數組中元素并存儲在val中。

int get_vec_element (*vec v, size_t idx, data_t *val) { if (idx 》= v-》len) return 0; *val = v-》data[idx]; return 1; }

我們將以以下代碼為例開始一步步優化程序。

void combine1(vec_ptr v, data_t *dest) { long int i; *dest = NULL; for (i = 0; i 《 vec_length(v); i++) { data_t val; get_vec_element(v, i, &val); *dest = *dest * val; } }

5.2 分析代碼

get_vec_element函數的作用是獲取下一個元素,在get_vec_element函數中,每次循環都要與v-》len作比較,防止越界。進行邊界檢查是個好習慣,但是每次都進行就會造成效率降低。

5.3 改進代碼

我們可以把求向量長度的代碼移到循環體外,同時抽象數據類型增加一個函數get_vec_start。這個函數返回數組的起始地址。這樣在循環體中就沒有了函數調用,而是直接訪問數組。

data_t *get_vec_start(vec_ptr v) { return v-data; } void combine2 (vec_ptr v, data_t *dest) { long i; long length = vec_length(v); data_t *data = get_vec_start(v); *dest = NULL; for (i=0;i 《 length;i++) { *dest = *dest * data[i]; } }

6. 循環展開

6.1 示例代碼

我們在combine2的代碼上進行改進。

6.2 分析代碼

循環展開是通過增加每次迭代計算的元素的數量,減少循環的迭代次數。

6.3 改進代碼

void combine3(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 = NULL; /* 一次循環處理兩個元素 */ for (i = 0; i 《 limit; i+=2) { acc = (acc * data[i]) * data[i+1]; } /* 完成剩余數組元素的計算 */ for (; i 《 length; i++) { acc = acc * data[i]; } *dest = acc; }

在改進后的代碼中,第一個循環每次處理數組的兩個元素。也就是每次迭代,循環索引i加2,在一次迭代中,對數組元素i和i+1使用合并運算。一般我們稱這種為2×1循環展開,這種變換能減小循環開銷的影響。

注意訪問不要越界,正確設置limit,n個元素,一般設置界限n-1

7. 累計變量,多路并行

7.1 示例代碼

我們在combine3的代碼上進行改進。

7.2 分析代碼

對于一個可結合和可交換的合并運算來說,比如說整數加法或乘法,我們可以通過將一組合并運算分割成兩個或更多的部分,并在最后合并結果來提高性能。

特別注意:不要輕易對浮點數進行結合。浮點數的編碼格式和其他整型數等都不一樣。

7.3 改進代碼

void combine4(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 (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; }

上述代碼用了兩次循環展開,以使每次迭代合并更多的元素,也使用了兩路并行,將索引值為偶數的元素累積在變量acc0中,而索引值為奇數的元素累積在變量acc1中。因此,我們將其稱為”2×2循環展開”。運用2×2循環展開。通過維護多個累積變量,這種方法利用了多個功能單元以及它們的流水線能力

8. 重新結合變換

8.1 示例代碼

我們在combine3的代碼上進行改進。

8.2 分析代碼

到這里其實代碼的性能已經基本接近極限了,就算做再多的循環展開性能提升已經不明顯了。我們需要換個思路,注意下combine3代碼中第12行的代碼,我們可以改變下向量元素合并的順序(浮點數不適用)。重新結合前combine3代碼的關鍵路徑如下圖所示。

7bfbd612-57dd-11eb-8b86-12bb97331649.png

combine3代碼的關鍵路徑

8.3 改進代碼

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 = IDENT; /* Combine 2 elements at a time */ for (i = 0; i 《 limit; i+=2) { acc = acc OP (data[i] OP data[i+1]); } /* Finish any remaining elements */ for (; i 《 length; i++) { acc = acc OP data[i]; } *dest = acc; }

重新結合變換能夠減少計算中關鍵路徑上操作的數量,這種方法增加了可以并行執行的操作數量了,更好地利用功能單元的流水線能力得到更好的性能。重新結合后關鍵路徑如下所示。

7c1b2288-57dd-11eb-8b86-12bb97331649.png

combine3重新結合后關鍵路徑

9 條件傳送風格的代碼

9.1 示例代碼

void minmax1(long a[],long b[],long n){ long i; for(i = 0;i,n;i++){ if(a[i]》b[i]){ long t = a[i]; a[i] = b[i]; b[i] = t; } } }

9.2 分析代碼

現代處理器的流水線性能使得處理器的工作遠遠超前于當前正在執行的指令。處理器中的分支預測在遇到比較指令時會進行預測下一步跳轉到哪里。如果預測錯誤,就要重新回到分支跳轉的原地。分支預測錯誤會嚴重影響程序的執行效率。因此,我們應該編寫讓處理器預測準確率提高的代碼,即使用條件傳送指令。我們用條件操作來計算值,然后用這些值來更新程序狀態,具體如改進后的代碼所示。

9.3 改進代碼

void minmax2(long a[],long b[],long n){ long i; for(i = 0;i,n;i++){ long min = a[i] 《 b[i] ? a[i]:b[i]; long max = a[i] 《 b[i] ? b[i]:a[i]; a[i] = min; b[i] = max; } }

在原代碼的第4行中,需要對a[i]和b[i]進行比較,再進行下一步操作,這樣的后果是每次都要進行預測。改進后的代碼實現這個函數是計算每個位置i的最大值和最小值,然后將這些值分別賦給a[i]和b[i],而不是進行分支預測。

10. 總結

我們介紹了幾種提高代碼效率的技巧,有些是編譯器可以自動優化的,有些是需要我們自己實現的。現總結如下。

消除連續的函數調用。在可能時,將計算移到循環外。考慮有選擇地妥協程序的模塊性以獲得更大的效率。

消除不必要的內存引用。引入臨時變量來保存中間結果。只有在最后的值計算出來時,才將結果存放到數組或全局變量中。

展開循環,降低開銷,并且使得進一步的優化成為可能。

通過使用例如多個累積變量和重新結合等技術,找到方法提高指令級并行。

用功能性的風格重寫條件操作,使得編譯采用條件數據傳送。

原文標題:9個提高代碼運行效率的小技巧你知道幾個?

文章出處:【微信公眾號:嵌入式ARM】歡迎添加關注!文章轉載請注明出處。

責任編輯:haq

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 程序
    +關注

    關注

    117

    文章

    3846

    瀏覽量

    85240
  • 代碼
    +關注

    關注

    30

    文章

    4968

    瀏覽量

    73965

原文標題:9個提高代碼運行效率的小技巧你知道幾個?

文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    資產管理怎么提高效率

    資產管理怎么提高效率資產管理效率提升需從流程自動化、數據可視化、資源優化三方面入手,通過智能工具替代人工操作。廣州杰眾智能科技有限公司的RFID技術方案,可使資產盤點效率提升90%,閑置資產利用率
    的頭像 發表于 01-28 16:49 ?454次閱讀
    資產管理怎么<b class='flag-5'>提高效率</b>

    提高系統效率幾個誤解解析

    才會提高系統效率。所以在通信系統中一般只打開指令CACHE,數據CACHE即使打開也只局限在部分存儲空間,如堆棧部分。同時也要求程序設計 要兼顧CACHE的容量及塊大小,這涉及到關鍵代碼循環體的長度
    發表于 12-15 06:09

    FLASH中的代碼是如何得到運行的呢

    以ARM為例: ARM-cortex-M3/4的單片機(比如STM32 等):該類單片機的代碼在nor flash中,cortex內核可以直接運行,不需要將代碼加載到ram中運行
    發表于 12-04 08:06

    HarmonyOS應用代碼混淆技術方案

    代碼混淆技術可以增加代碼的復雜性和模糊性,從而提高攻擊者分析代碼的難度。
    的頭像 發表于 11-21 16:17 ?5604次閱讀
    HarmonyOS應用<b class='flag-5'>代碼</b>混淆技術方案

    通過優化代碼提高MCU運行效率

    存中的地址按照其類型自然對齊,非對齊訪問在某些架構上非常慢甚至會導致異常。 函數設計 減少函數調用深度。 參數盡量少,通常4以內可以通過寄存器傳遞,效率更高。
    發表于 11-12 08:21

    怎樣提高單相交流電機的效率

    提高單相交流電機效率是一涉及設計優化、材料選擇、運行維護等多方面的系統工程。以下從技術原理、實用方法和前沿趨勢三維度,系統闡述提升
    的頭像 發表于 11-12 07:40 ?607次閱讀

    在極海APM32系列MCU中如何把代碼重定位到SDRAM運行

    在有些情況下,我們想要把代碼放到SDRAM運行。下面介紹在APM32的MCU中,如何把代碼重定位到SDRAM運行。對于不同APM32系列的MCU,方法都是一樣的。
    的頭像 發表于 11-04 09:14 ?5175次閱讀
    在極海APM32系列MCU中如何把<b class='flag-5'>代碼</b>重定位到SDRAM<b class='flag-5'>運行</b>

    提高RISC-V在Drystone測試中得分的方法

    速度。 優化內存系統:優化內存控制器設計,提高內存的讀寫速度、降低延遲,或者增大內存帶寬。 優化指令集和編譯器:通過優化或定制指令集,以及優化編譯器來生成更高效的機器代碼。 軟件優化:對運行的軟件進行優化
    發表于 10-21 13:58

    諧波會對新能源設備的運行效率產生哪些影響?

    諧波對新能源設備運行效率的影響,本質是通過 增加額外損耗、干擾控制邏輯、導致設備降額運行 三大路徑實現,覆蓋光伏、風電、儲能及配套并網設備,最終表現為 “轉換效率下降、出力受限、隱性能
    的頭像 發表于 10-14 16:47 ?810次閱讀

    推薦5讓測試效率翻倍的MCP

    推薦5讓測試效率翻倍的MCP
    的頭像 發表于 09-19 10:02 ?633次閱讀
    推薦5<b class='flag-5'>個</b>讓測試<b class='flag-5'>效率</b>翻倍的MCP

    如何提高光刻膠殘留清洗的效率

    提高光刻膠殘留清洗效率需要結合工藝優化、設備升級和材料創新等多方面策略,以下是具體方法及技術要點:1.工藝參數精準控制動態調整化學配方根據殘留類型(正膠/負膠、厚膜/薄膜)實時匹配最佳溶劑組合。例如
    的頭像 發表于 09-09 11:29 ?989次閱讀
    如何<b class='flag-5'>提高</b>光刻膠殘留清洗的<b class='flag-5'>效率</b>

    嵌入式系統中,FLASH 中的程序代碼必須搬到 RAM 中運行嗎?

    嵌入式系統里,FLASH 中的程序代碼并非必須搬到 RAM 中運行,這得由硬件配置、實際性能需求和應用場景共同決定。就像很多低端單片機,無論是依賴片內 Flash 還是外掛的 SPI NOR
    的頭像 發表于 08-06 10:19 ?1365次閱讀
    嵌入式系統中,FLASH 中的程序<b class='flag-5'>代碼</b>必須搬到 RAM 中<b class='flag-5'>運行</b>嗎?

    同樣的代碼在官方開發板上運行正常,在自己板子上就跑不起來,怎么辦?

    UART交互,CLI代碼所在目錄為:SDK安裝目錄examplesperipheralcli。如果這2程序能運行正常,說明你的焊接問題
    的頭像 發表于 05-12 15:26 ?771次閱讀
    同樣的<b class='flag-5'>代碼</b>在官方開發板上<b class='flag-5'>運行</b>正常,在自己板子上就跑不起來,怎么辦?

    RAKsmart企業服務器上部署DeepSeek編寫運行代碼

    在RAKsmart企業服務器上部署并運行DeepSeek模型的代碼示例和詳細步驟。假設使用 Python + Transformers庫 + FastAPI實現一基礎的AI服務。主機推薦小編為您整理發布RAKsmart企業服務
    的頭像 發表于 03-25 10:39 ?699次閱讀

    功率設備控制可使用過零檢芯片CN71102提高轉換效率

    功率設備控制可使用過零檢芯片CN71102提高轉換效率
    的頭像 發表于 03-25 09:43 ?817次閱讀
    功率設備控制可使用過零檢芯片CN71102<b class='flag-5'>提高</b>轉換<b class='flag-5'>效率</b>