如何在ARM下進行高效的C程式設計?

2021-08-29 20:29:41 字數 4504 閱讀 5089

如何在arm下進行高效的c程式設計?

1.對區域性變數、函式引數和返回值要使用signed和unsigned int型別。這樣可以避免型別轉換,而且可高效地使用arm的32位資料操作指令。

2.最高效的迴圈體形式是減計數到零(counts down to zero)的do-while迴圈。

3.展開重要的迴圈來減少迴圈的開銷。

4.不要依賴編譯器來優化掉重複的儲存器訪問。指標別名會阻止編譯器的這種優化。

5.盡可能把函式引數的個數限制在4個以內。如果函式引數都存放在暫存器內,那麼函式呼叫就會快得多。

6.按元素尺寸從小到大排列的方法來安排結構體,特別是在thumb模式下編譯。

7.不要使用位域,可以用掩碼和邏輯操作來替代。

8.避免除法,可以用倒數的乘法來替代。

9.避免邊界不對齊的資料。如果資料有可能邊界不對齊,那麼就要使用char *指標型別來訪問。

10.在c編譯器中使用內嵌彙編可以利用到c編譯器本來不支援的指令或優化。

一、 資料型別使用上的優化

1.區域性變數

乙個char型別的資料比int型別的資料占用更小的暫存器空間或者更小的arm堆疊空間。這兩種設想對於arm來說,都是錯誤的。所有的arm暫存器都是32位的,所有的堆疊入口至少是32位的。當我們執行i++,要利用當i=255後,i++=0這個條件時,可以把它定義為char型別。

2.函式引數

儘管寬和窄的函式呼叫規則各有其優點,但char或short型別的函式引數和返回值都會產生額外的開銷,導致效能的下降,並增加了**尺寸。所以,即使是傳輸乙個8位的資料,函式引數和返回值使用int型別也會更有效。

總結:1)對於存放在暫存器中的區域性變數,除了8位或16位的算術模運算外,盡量不要使用char和short型別,而要使用有符號或無符號int型別。除法運算時使用無符號數執行速度更快。

2)對於存放在主儲存器中的陣列和全域性變數,在滿足資料大小的前提下,應盡可能使用小尺寸的資料型別,這樣可以節省儲存空間。armv4體系結構可以有效地裝載和儲存所有寬度的資料,並可以使用遞增陣列指標來有效地訪問陣列。對於short型別陣列,要避免使用陣列基位址的偏移量,因為ldrh指令不支援偏移定址。

3)通過讀取陣列或全域性變數並賦給不同型別的區域性變數時,或者把區域性變數寫入不同型別的陣列或者全域性變數時,要進行顯式資料型別轉換。這種轉換使編譯器可以明確、快速地處理,把儲存器中資料寬度比較窄的資料型別擴充套件,並賦給暫存器中較寬的型別。

4)由於隱式或者顯式的資料型別轉換通常會有額外的指令週期開銷,所以在表示式中應盡量避免使用。load和store指令一般不會產生額外的轉換開銷,因為load和store指令是自動完成資料型別轉換的。

5)對於函式引數和返回值應盡量避免使用char和short型別。即使引數範圍比較小,也應該使用int型別,以防止編譯器做不必要的型別轉換。

二、c迴圈結構

在arm上,乙個迴圈其實只要2條指令就足夠了:

一條減法指令,進行迴圈減法計數,同時設定結果的條件標誌;

一條條件分支指令。

這裡的關鍵是,迴圈的終止條件應為減計數到零,而不是計數增加到某個特定的限制值。由於減計數結構已儲存在條件標誌裡,與零比較的指令就可以省略了。由於不用i作為陣列的下標索引,採用減計數就沒有任何問題了。

總而言之,無論對於有符號的迴圈計數值,都應使用i!=0作為迴圈的結束條件。對有符號數i,這比使用條件i>0少了一條指令。

總結:1) 使用減計數到零的迴圈結構,這樣編譯器就不需要分配乙個暫存器來儲存迴圈終止值,而且與0比較的指令也可以省略。

2) 使用無符號的迴圈計數值,迴圈繼續的條件為i!=0而不是i>0,這樣可以保證迴圈開銷只有兩條指令。

3) 如果事先知道迴圈體至少會執行一次,那麼使用do-while迴圈要比for迴圈要好,這樣可以使編譯器省去檢查迴圈計數值是否為零的步驟。

4) 展開重要的迴圈體可降低迴圈開銷,但不要過度展開,如果迴圈的開銷對整個程式來說佔的比例很小,那麼迴圈展開反而會增加**量並降低cache的效能。

5) 盡量使陣列的大小是4或8的倍數,這樣可以容易的以2,4,8次等多種選擇展開迴圈,而不需要擔心剩餘陣列元素的問題。

三、暫存器分配

高效的暫存器分配:應該盡量限制函式內部迴圈所用區域性變數的數目,最多不超過12個,這樣,編譯器就可以把這些變數都分配給arm暫存器。

四、函式呼叫

4暫存器規則:帶有4個或者更少引數的函式,要比多於4個引數的函式執行效率高得多。對帶有少於4個引數的函式來說,編譯器可以用暫存器傳遞所有的引數;而對於多於4個引數的函式,函式呼叫者和被呼叫者必須通過訪問堆疊來傳遞一些引數。

如果函式體積很小,只用到很少的暫存器,那麼還有一些其他的方法來減少函式呼叫的開銷。可以把呼叫函式和被呼叫函式放在同乙個c檔案中,這樣編譯器就知道了被呼叫函式生成的**,並以此對呼叫函式進行一些優化。

總結:1) 盡量限制函式的引數,不要超過4個,這樣函式呼叫的效率會更高。也可以將幾個相關的引數組織在乙個結構體中,用傳遞結構體指標來代替多個引數。

2) 把比較小的被呼叫函式和呼叫函式放在同乙個原始檔中,並且要先定義,後呼叫,編譯器就可以優化函式呼叫或者內聯較小的函式。

3) 對效能影響較大的重要函式可使用關鍵字_inline進行內聯。

五、指標別名

定義:當2個指標指向同乙個位址物件時,這2個指標被稱作該物件的別名(alias)。如果對其中乙個指標進行寫入,就會影響從另乙個指標的讀出。在乙個函式中,編譯器通常不知道哪乙個指標是別名,哪乙個不是;或哪乙個指標有別名,哪乙個沒有。

避免指標別名:

1) 不要依賴編譯器來消除包含儲存器訪問的公共子表示式,而應建立乙個新的區域性變數來儲存這個表示式的值,這樣可以保證只對這個表示式求一次值;

2) 避免使用區域性變數的位址,否則對這個變數的訪問效率會比較低。

六、結構體安排

獲得高效結構體的原則:

1) 把所有8位大小的元素安排在結構體的前面;

2) 以此安排16位、32位和64位的元素;

3) 把所有陣列和比較大的元素安排在結構體最後;

4) 對於一條指令,如果結構體太大而不能訪問所有的元素,那麼把元素組織到乙個子結構體中。編譯器可以維持單獨的子結構體的指標。

總結:結構體元素要按照元素的大小來排列,以最小的元素放在開始,最大的元素安排在最後;避免使用很大的結構體,可以用層次化的小結構體來代替;為了提高可移植性,人工對api的結構體增加填充位,這樣,結構體的安排將不會依賴與編譯器;在api的結構體中要謹慎使用列舉型別。乙個列舉型別的大小是編譯器相關的。

七、位域

注意事項:

1) 應避免使用位域,而使用#define或者enum來定義遮蔽位;

2) 使用整型邏輯運算and、or、「異或」操作和遮蔽對位域進行測試、取反和設定操作。這些操作編譯效率高,還可以同時對多個位域進行測試、取反和設定。

八、邊界不對齊資料和位元組排列方式(大/小端)

邊界不對齊資料和位元組排列方式這2個問題,可使記憶體訪問和移植問題複雜化。須考慮陣列指標是否邊界對齊,arm配置是大端(big-endian),還是小端(little-endian)的儲存器系統。

總結:1) 盡量避免使用邊界不對齊的資料;

2) 使用型別char *可指向任意位元組邊界的資料。通過讀位元組來訪問資料,使用邏輯操作來組合資料,這樣**就不會依賴於邊界是否對齊或者arm的位元組排列方式的配置;

3) 為了快速訪問邊界不對齊的結構體,可以根據指標邊界和處理器的位元組排序方式寫出不同的程式變體。

九、除法

arm硬體上不支援除法指令,當**中出現除法運算時,arm編譯器會呼叫c庫函式(有符號的除法呼叫_rt_sdiv,無符號的呼叫_rt_udiv),來實現除法操作。有許多不同型別的除法程式來適應不同的除數和被除數。

總結:1) 盡可能避免使用除法。對環形緩衝區的處理可以不用除法。

2) 如果不能避免除法運算,那麼盡可能考慮使用除法程式同時產生商n/d和餘數n%d的好處。

3) 對於重複對同一除數d的除法,預先計算好s=(2k-1)/d。可用乘以s的2k位乘法來代替除以d的k位無符號整數除法。

4)使用2的整數次冪作除數。當2的整數次冪做除數時,編譯器會自動將除法運算轉換成移位運算。所以在編寫程式演算法時,盡量使用2的整數次冪做除數。

5)求餘運算。可以將一些典型的求餘運算進行轉換,以避免在程式中使用除法運算。如:

uint counter1(uint count)

return (++count%60);

轉換成:

uint counter2(uint count)

if (++count >=60)

count=0;

return (count);

十、浮點運算

大多數arm處理器硬體上並不支援浮點運算。這樣在乙個對**敏感的嵌入式應用系統中,可節省空間和降低功耗。除了硬體向量浮點累加器vfp和arm7500fe上的浮點累加器fpa外,c編譯器必須在軟體上提供浮點支援。

十一、內聯函式和內嵌彙編

高效地呼叫函式,使用內聯函式可以完全去除函式呼叫的開銷,另外許多編譯器允許在c源程式中使用內嵌彙編。使用包含彙編的內嵌函式,可以使編譯器支援通常不能有效使用的arm指令和優化方法。

內聯函式和內嵌彙編最大的好處是,可以實現一些在c語言部分中通常難以完成的操作。使用內聯函式要比使用#define巨集定義更好,因為後者不檢查函式引數和返回值的型別。

基於ARM的高效C語言程式設計

arm處理器提高執行速度和減小 尺寸是嵌入式軟體設計的關鍵需求,以其高效能 低功耗 低成本等優勢被廣泛應用於各種成功的32位嵌入式系統中。儘管大多數的arm編譯器和偵錯程式都帶有效能優化工具,但是為了保證其正確性,編譯器必須是穩妥和安全的,而且它還受到處理器自身結構的限制。因此,程式設計人員必須在理...

如何在linux下進行contiki移植之環境搭建

各位喜歡contiki 的guys 歡迎一起學習 contiki 小菜我最近也在研究 contiki 當然作為初學者的我遇到了很多問題,遇到問題怎麼辦?當然 不是逃避而是想辦法去解決問題!國內資料確實很少,很少提及在 linux 下開發,所以我想談談 inux 下如何開發!首先交待一下學習conti...

Nginx環境下如何在PHP進行安全設定

當下nginx越發流行,寶塔面板 oneinstack lnmp等整合環境大多數都使用nginx作為web服務,nnginx php情況下使用fpm fastcgi 程序管理器 來執行php,這篇文章拋開php程式寫法不嚴謹造成的本身漏洞,單從web服務和php本身設定來加強php程式安全。php提...