程序設計優化

(1)、求餘運算。
a=a%8;
能夠改成:
a=a&7;
說明:位操做只需一個指令週期便可完成,而大部分的C編譯器的「%」運算均是調用子程序來完成,代碼長、執行速度慢。一般,只要求是求2n方的餘數,都可使用 位操做的方法來代替。html

2)、用移位實現乘除法運算
a=a*4;
b=b/4;
能夠改成:
a=a<<2;
b=b>>2;算法

a=a*9
能夠改成:
a=(a<<3)+a數組

3)使用盡可能小的數據類型
4)使用自加、自減指令
http://blog.chinaunix.net/uid-20361370-id-1962787.html
http://wenku.baidu.com/view/917c9fd6195f312b3169a564.html?re=view網絡

ARM 相關
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0198e/ch05s02s03.htmljsp

http://www.atarm.com/doc/Efficient_programming_techniques_for_ARM.pdf
一篇講ARM優化的文章,不錯
http://blog.csdn.net/shengnan_wu/article/details/8287428
展開:
下面是網絡上收集關於ARM的C代碼優化方法,在嵌入開發中應該有用:函數

[聲明:如下方法非本人發現和總結,均爲有心之人無私貢獻,謝謝他們的勞動與分享!]佈局

========================================================================================post

C數據類型性能

  1. C語言的程序優化與編譯器和硬件系統都有關係,設置某些編譯器選項是最直接最簡單的優化方式。優化

    在默認的狀況下,armcc是所有優化功能,而GNU編譯器的默認狀態下優化都是關閉的

    ARM C編譯器中定義的char類型是8位無符號的,有別於通常流行的編譯器默認的char是8位有符號的。

    因此循環中用char變量和條件 i ≥ 0時,就會出現死循環。爲此,能夠用fsigned - char(for gcc)或者-zc(for armcc)把char改爲signed。

    其餘的變量類型以下:

    char 無符號8位字節數據

    short 有符號16位半字節數據

    int 有符號32位字數據

    long 有符號32位字數據

    long long 有符號64位雙字數據

  2. 關於局部變量

    大多數ARM數據處理操做都是32位的,局部變量應儘量使用32位的數據類型(int或long)就算處理8位或者16位的數值,也應避免用char和short以求邊界對齊,除非是利用char或者short的數據一出歸零特性(如255+1=0,多用於模運算)。不然,編譯器將要處理大於short和char取值範圍的狀況而添加代碼。

另外對於表達式的處理也要格外當心,以下例子

[html] view plaincopy
01.short checksum_v3(short * data){

  1. unsigned int i;
  2. short sum = 0;
  3. for(i = 0; i < 64 ; i++){
  4. sum = (short)( sum + data );
  5. //這裏表達式式整形的,因此返處理非32位數據時,
  6. //要當心處理數據類型的轉換。
  7. //原來short+short=int 但 int +int=int。。奇怪的處理
  8. }
  9. return sum;
    11.}
    同時如上例的程序所示,這樣在循環體中的每次運算都要進行類型轉換,會下降程序的效率,能夠先把其看成int來運算,而後再返回一個short類型。
    同時,因爲處理的data[]是一個short型數組,用LDRH指令的話,不能使用桶型移位器,因此只能先進行偏移量的覺得操做,而後再尋址,也會形成不佳的性能。解決的方法是用指針代替數組操做。以下:

[html] view plaincopy
01.short checksum_v4(short * data){

  1. unsigned int i;
  2. int sum = 0;
  3. for( i = ; i<64; i++) {
  4. sun += ( data ++);
  5. }
  6. return (short) sum;
    08.}

  7. 關於函數參數類型
    函數參數和返回值應儘可能使用int類型。
    另外,對於調用頻率較低的全局變量,儘可能使用小的數據類型以節省空間。

C循環結構

◎ 使用減數到零的循環體,以節省指令和寄存器的使用。
◎ 使用無符號的循環計數值,並用條件 i != 0停止。
◎ 若是循環體至少執行一次,用優先選用do-while。
◎ 適當狀況下展開循環體。
◎ 儘可能使用數組的大小是4或8的備述,用此倍數展開循環體 寄存器分配
◎ 儘可能限制函數內部循環所用局部變量的數目,最多不超過12個,以便編譯器能把變量分配到寄存器。

◎ 能夠引導編譯器,經過查看是否屬於最內層循環的便賴寧嘎來去定某個變量的重要性。

函數調用

ARM中的函數前4個整型參數經過寄存器r0、r一、r二、r3來傳遞,隨後的整型參數經過堆棧來傳遞。(full desceding stack)。

◎ 儘可能限制函數參數,不要超過四個,也能夠把相關的參數組織在結構體傳遞。
◎ 把比較小的被調用函數和調用函數放在同一個源文件中,而且限定之後調用,編譯器能進行優化。
◎ 用_inline內聯性能影響較大的重要函數。

指針別名

◎ 用一個局部變量來保存公共子表達式的值,保證該表達式只求一次值。
◎ 避免使用局部變量的地址,不然訪問這個變量的效率較低。

結構體的安排

◎ 小的元素放在結構體的開始,大的元素放在結構體的最後
◎ 避免使用過大的結構體,用層次話的小結構體代替。
◎ 人工對API的結構體增長填充位以提升移植性。
◎ 枚舉類型要慎用,由於它的大小與編譯器相關。

位域

◎ 儘可能用define或者enum來代替位域
◎ 用邏輯運算來丟位域操做 邊界不對齊數據和字節排列方式
◎ 儘可能避免使用邊界不對齊數據;
◎ 用char× 可指向任意字節對齊的的數據,與邏輯運算配合,可訪問任意邊界和排列的數據。

除法

◎ 一堆算法,很差寫,總的來講是以乘代除,配合移位運算。 內聯函數和內嵌彙編
◎ 沒什麼好寫的,就是內聯減小調用開銷,內嵌彙編提升運行效率。

總結

總的來講,高級語言的優化和編譯器、硬件結構有關。

硬件上,ARM通常爲32位總線,以32位訪問數據的速度較快。局部變量和其餘經常使用的變量要儘可能利用32位的int類型,組織結構體時,也要注意元素的位置(小前大後),以節省空間。另外,因爲ARM指令可條件執行,因此充分利用cpsr會使程序更有效率。同時注意好類型之間的運算,儘可能減小轉型操做。任什麼時候候除法和取模運算能夠同時取得結果而不會額外增長運算過程,但單單對於除法,仍是以乘代除比較划算。

對於編譯器,armcc聽從ATPCS的要求,第一到第四個參數依次經過r0~r4傳遞,其餘參數經過堆棧傳遞,返回值用r0傳遞,所以,爲了把大部分操做放在寄存器中完成,參數最好很少與4個。另外,可用的通用寄存器有12個,因此儘可能將局部變量控制在12個以內,效率上會獲得提高。同時,因爲編譯器比較保守,指針別名會引發多餘的讀操做,因此儘可能少用。

============================================================================================

數據類型

存放在寄存器中的局部變量(尤爲是循環變量)應儘量使用32位數據類型int(=long),8位變量不節省任何空間和時間;

即便傳輸一個8位數據,函數參數和返回值使用32位類令會更有效;

能用指針遞增尋址就不用數組下表遞增尋址a=data[i++]不如a=*(data++);

除法運算使用無符號數更快;

存放在存儲器中的數組和全局變量,儘量使用小尺寸數據類型;

short型數組儘可能避免使用數組基地址的偏移量,由於LDRH指令不支持偏移尋址;

存儲器變量和寄存器變量相互賦值時使用顯式類型轉換,其餘狀況下避免沒必要要的類型轉換;

循環結構

採用減計數循環比增計數循環更好,終止條件儘可能寫 i != 0 ;循環變量起始值是變量且不等於0的狀況下用do-while循環更優(終止條件在後);

若循環體過於簡單,好比少於4個週期,可展開循環體(重複寫幾遍循環體代碼),以避免循環體代碼還不如循環自己執行週期長;

儘可能限制函數內部循環所用局部變量的數據,最多不要超過12個,這樣編譯器就能夠把他們都分配給ARM寄存器;

函數調用

儘可能限制函數的參數,不要超過4個。能夠將幾個相關參數組織在一個結構體中;

把較小的被調函數和調用函數放在一個文件中,並先定義再調用;

對性能影響較大的重要函數可以使用_inline進行內聯;

指針別名

創建一個新的局部變量來保存包含存儲器訪問的表達式,這樣能夠保證只對這個表達式求一次值,例如int a=data[n];b+=a;c+=a; 比b+=data[n];c+=data[n];好避免使用局部變量的地址,不然對這個變量的訪問效率會比較低;
結構體安排

結構體元素要按照元素從小到大排序;
避免使用很大的結構體,能夠用層次化的小結構體來代替

注:針對ARMv4以上版本

===========================================================================================
變量定義

32位ARM處理器的指令集支持有符號/無符號的8位、16位、32位整型和浮點型變量類型,這不只能夠節省代碼,並且能夠提升代碼的運行效率。按照做用範圍的不一樣,C語言的變量能夠劃分爲全局變量和局部變量。ARM編譯器一般將全局變量定位在存儲空間中,局部變量分配給通用寄存器。

在全局變量聲明時,須要考慮最佳的存儲器佈局,使得各類類型的變量能以32位的空間位基準對齊,從而減小沒必要要的存儲空間浪費,提升運行效率。如:

01.char a; char a;
02.short b; char c;
03.char c; short b;
04.int d; int d;

對於局部變量,要儘可能不使用32位之外的變量類型。當一個函數的局部變量數目很少時,編譯器會把局部變量分配給內部寄存器,每一個變量佔一個32位的寄存器。這樣short和char類型的變量不但起不到節省空間的做用,反而會耗費更多的指令週期來完成short和char的存取操做。C語言代碼及其編譯結果以下所示:

01.int wordinc(int a) wordinc:
02.{

  1. return (a+1); ADD a1, a1,#1
    04.}
  2. 06.short shortinc(short a) shortinc:
    07.{ ADD a1, a1,#1
  3. return a+1; MOV a1, a1, LSL #16
    09.} MOV a1, a1, ASR #16
  4. MOV  PC,LR
  5. 12.char charinc(char a) charinc:
    13.{ ADD a1, a1,#1
  6. return a+1; AND a1, a1,#&FF
    15.} MOV PC,LR
    條件執行
    條件執行是程序中必不可少的基本操做。典型的條件執行代碼序列是由一個比較指令開始的,接下來是一系列相關的執行語句。ARM中的條件執行是經過對運算結果標誌位進行判斷實現的,一些帶標誌位的運算結果中,N和Z標誌位的結果與比較語句的結果相同。儘管在C語言中沒有帶標誌位的指令,但在面向ARM的C語言程序中,若是運算結果是與0做比較,編譯器會移去比較指令,經過一條帶標誌位指令實現運算和判斷。例如:

01.int g(int x, int y) g:
02.{ ADDS a1, a1, a2

  1. if(x+y<0) MOVPL a1, #0
  2. return 1; MOVMI a1, #1
  3. else MOV pc, lr
  4. return 0;
    07.}
    所以,面向ARM的C語言程序設計的條件判斷應當儘可能採用「與0比較」的形式。C語言中,條件執行語句大多數應用在if條件判斷中,也有應用在複雜的關係運算(<,==,>等)及位操運算(&&,!,and等)中的。面向ARM的C語言程序設計中,有符號型變量應儘可能採起x<0、x>=0、x==0、x!=0的關係運算;對於無符號型的變量應採用x==0、x!=0(或者x>0)關係運算符。編譯器均可以對條件執行進行優化。
    對於程序設計中的條件語句,應儘可能簡化if和else判斷條件。與傳統的C語言程序設計有所不一樣,面向ARM的C語言程序設計中,關係表述中相似的條件應該集中在一塊兒,使編譯器可以對判斷條件進行優化。
    循環

循環是程序設計中很是廣泛的結構。在嵌入式系統中,微處理器執行時間在循環中運行的比例較大,所以關注循環的執行效率是很是必要的。除了在保證系統正確工做的前提下儘可能簡化核循環體的過程之外,正確和高效的循環結束標誌條件也很是重要。按照以上所述的「與0比較」原則,程序中的循環結束條件應該是「減到0」的循環,結束條件儘可能簡單。應儘量在關鍵循環中採起上述的判斷形式,這樣能夠在關鍵循環中省去一些沒必要要的比較語句,減小沒必要要的開銷,提升性能。以下面二個示例:

01.int fact1(int n) int fact2(int n)
02.{ {

  1. int a, fact=1; int a, fact=1;
  2. for(a=1; a<=n; a++) for(a=n; a!=0; a++)
  3. fact *= a;                       fact *= a;
  4. return(fact); return(fact);
    07.} }
    fact1和fact2中經過定義局部變量a來減小對n的load/store操做。fact2函數遵循了「與0比較」原則,省去了fact1編譯結果中的比較指令,而且,變量n在整個循環過程不參與運算,也不須要保存。因爲省去了寄存器分配,從而給其餘部分程序的編譯帶來了方便,提升了運行效率。

「減到0」的方法一樣適用於while和do語句。若是一個循環體只循環幾回,能夠用展開的方法提升運行效率。當循環展開後,不須要循環計數器和相關的跳轉語句,雖然代碼的長度有所增長,可是獲得了更高的執行效率。

除法和求餘

ARM指令集中沒有提供整數的除法,除法是由C語言函數庫中的代碼(符號型_rt_sdiv和無符號型的_rt_udiv)實現的。一個32位數的除法須要20~140個週期,依賴於分子和分母的取值。除法操做所用的時間是一個時間常量乘每一位除法所須要的時間:

Time(分子/分母)=C0+C1×log2(分子/分母)
=C0+C1×(log2(分子)-log2(分母))
因爲除法的執行週期長,耗費的資源多,程序設計中應當儘可能避免使用除法。如下是一些避免調用除法的變通辦法:

(1)在某些特定的程序設計時,能夠把除法改寫爲乘法。例如:(x/y)>z,在已知y是正數並且y×z是整數的狀況下,就能夠寫爲x>(z×y)。

(2)儘量使用2的次方做爲除數,編譯器使用移位操做完成除法,如128就比100更加適合。在程序設計中,使用無符號型的除法要快於符號型的除法。

(3)使用求餘運算的一個目的是爲了按模計算,這樣的操做有時可使用if的判斷語句來完成,考慮以下的應用:

(4)對於一些特殊的除法和求餘運算,採用查找表的方法也能夠得到很好的運行效果。

在除以某些特定的常數時,編寫特定的函數完成此操做會比編譯產生的代碼效率高不少。ARM的C語言庫中就有二個這樣的符號型和無符號型數除以10的函數,用來完成十進制數的快速運算。在toolkit子目錄的examples\explasm\div.c和examples\thumb\div.c文件中,有這二個函數的ARM和Thumb版本。

==========================================================================================

1 程序運行速度優化

程序運行速度優化的方法可分爲如下ARM幾大類。

1.1 通用的優化方法

(1)減少運算強度

利用左/ 右移位操做代替乘/ 除2 運算:一般須要乘以ARM或除以2 的冪次方均可以經過左移或右移n 位來完成。實際上乘以任何一個整數均可以用移位和加法來代替乘法。ARM 7 中加法和移位能夠經過一條指令來完成,且執行時間少於乘法指令。例如: i = i × 5 能夠用i = (i<<2) + i 來代替。

利用乘法代替乘方運算:ARM7 核中內建有32 ×8 ARM乘法器, 所以能夠經過乘法運算來代替乘方運算以節約乘方函數調用的開銷。例如: i = pow(i, 3.0) 可用 i = i×i × i 來代替。
利用與運算代替求餘運算:有時能夠經過用與(AND )指令代替求餘操做(% )來提升效率。例如:i = i % 8 能夠用 i = i & 0x07 來代替。

(2)優化循環終止ARM條件

在一個循環結構中,循環的終止條件將嚴重影響着循環的效率,再加上ARM 指令的條件執行特性,因此在書寫循環的終止條件時應儘可能使用count-down-to-zero結構。這樣編譯器能夠用一條BNE (若非零則跳轉)指令代替CMP (比較)和BLE (若小於則跳轉)兩條指令,既減少代碼尺寸,又加快了運行ARM速度。

(3)使用inline 函數

ARM C 支持 inline 關鍵字,若是一個函數被設計ARM成一個inline 函數,那麼在調用它的地方將會用函數體來替代函數調用語句, 這樣將會完全省去函數調1.2 處理器相關的優化ARM方法用的開銷。使用inline 的最大缺點是函數在被頻繁調用時,代碼量將增大。

(1)保持流水線暢通

從前面的介紹可知,流水線延遲或阻斷會對處理器的性能形成影響,所以應該儘可能保持流水線暢通。流水線延遲難以免, 但能夠利用延遲週期進行其它ARM操做。

LOAD/STORE 指令中的自動索引(auto-indexing)功能就是爲利用ARM流水線延遲週期而設計的。當流水線處於延遲週期時, 處理器的執行單元被佔用, 算術邏輯單元ARM(ALU )和桶形移位器卻可能處於空閒狀態,此時能夠利用它們來完成往基址寄存器上加一個偏移量的操做,供後面的指令使用。例如:指令 LDR R1, [R2], #4 完成 R1= R2 及 R2 += 4 兩個操做,是後索引(post-indexing)的例子;而指令 LDR R1, [R2, #4]! 完成 R1 = (R2 + 4) 和 R2 +=4 兩個操做,是前索引(pre-indexing)的例子。

流水線阻斷的狀況可經過循環拆解等方法加以改善。一個循環能夠考慮拆解以減少跳轉指令在循環指令中所佔的比重, 進而提升代碼效率。下面以一個內存複製函數加以ARM說明。

01.void memcopy(char to, char from, unsigned int nbytes)
02.{

  1. while(nbytes--)ARM
  2. *to++ = *from++;
    05.}
    爲簡單起見,這裏假設nbytes 爲16 的ARM倍數(省略對餘數的處理)。上面的函數每處理一個字節就要進行一次判斷和跳轉, 對其中的循環體可做以下拆解:

01.void memcopy(char to, char from, unsigned int nbytes)
02.{

  1. while(nbytes) {
  2. *to++ = *from++;
  3. *to++ = *from++;ARM
  4. *to++ = *from++;
  5. *to++ = *from++;
  6. nbytes - = 4;
  7. }
    10.}
    這樣一來, 循環體中的指令數增長了,循環次數卻減小了。跳轉指令ARM帶來的負面影響得以削弱。利用ARM 7 處理器32 位字長的特性, 上述代碼可進一步做以下調整:

01.void memcopy(char to, char from, unsigned int nbytes)ARM
02.{

  1. int p_to = (int )to;
  2. int p_from = (int )from;
  3. while(nbytes) {
  4. *p_to++ = *p_from++;
  5. *p_to++ = *p_from++;
  6. *p_to++ = *p_from++;
  7. *p_to++ = *p_from++;
  8. nbytes - = 16;
  9. }
    12.}
    通過優化後,一次循環能夠處理16 個字節。跳轉指令帶來的影響ARM進一步獲得減弱。不過能夠看出, 調整後的代碼在代碼量方面有所增長。

(2)使用寄存器變量

CPU 對寄存器的存取要比對內存的存取快得多ARM, 所以爲變量分配一個寄存器, 將有助於代碼的優化和運行效率的提升。整型、指針、浮點等類型的變量均可以分配寄存器; 一個結構的部分或者所有也能夠分配寄存器。給循環體中須要頻繁訪問的變量分配寄存器也能在必定程度上提升程序效率。

1.3 指令集相關的優化方法

有時能夠利用ARM7 指令集的特色對程序ARM進行優化。

(1)避免除法

ARM 7 指令集中沒有除法指令,其除法是經過調用C 庫函數實現的。一個32 位的除法一般須要20~140 個時鐘週期。所以, 除法成了一個程序效率的瓶頸, 應儘可能避免使用。有些除法可用乘法代替,例如: if ( (x / y) > z)可變通爲 if ( x > (y × z)) 。在能知足精度,且存儲器空間冗餘的狀況下, 也可考慮使用查表法代替除法。當除數爲2 的ARM冪次方時, 應用移位操做代替除法。

(2)利用條件執行

ARM 指令集的一個重要特徵就是全部的指令都可包含一個可選的條件碼。當程序狀態寄存器(PSR )中的條件碼標誌知足指定條件時, 帶條件碼的指令才能執行。利用條件執行一般能夠省去單獨的判斷ARM指令,於是能夠減少代碼尺寸並提升程序效率。

(3)使用合適的變量類型

ARM 指令集支持有符號/ 無符號的8 位、16 位、32位整型及浮點型變量。恰當的使用變量的類型,不只能夠節省代碼,而且能夠提升代碼運行效率。應該儘量地避免使用char、short 型的ARM局部變量,由於操做8 位/16 位局部變量每每比操做3 2 位變量須要更多指令, 請對比下列3 個函數和它們的彙編代碼。

1.4 存儲器相關的優化方法

(1)用查表代替計算

在處理器資源緊張而存儲器資源相對富裕的狀況下, 能夠用犧牲存儲空間換取運行速度的辦法。例如須要頻繁計算正弦或餘弦函數值時,可預先將函數值計算出來置於內存中供之後ARM查找。

(2)充分利用片內RAM

一些廠商出產的ARM 芯片內集成有必定容量的RAM,如Atmel 公司的AT91R40807 內有128KB 的RAM,夏普公司的LH75400/LH75401 內有32KB 的RAM。處理器對片內RAM 的訪問速度要快於對外部RAM 的訪問,因此應儘量將程序調入片內RAM 中運行。若因程序太大沒法徹底放入片內RAM ,可考慮ARM將使用最頻繁的數據或程序段調入片內RAM 以提升程序運行效率。

1.5 編譯器相關的優化方法

多數編譯器都支持對程序速度和程序大小的優化,有些編譯器還容許用戶選擇可供優化的內容及優化的程度。相比前面的各類優化方法, 經過設置編譯器選項對程序進行優化不失爲一種簡單有效的途徑。

2 代碼尺寸優化

精簡指令集計算機的一個重要特色是指令長度固定, 這樣作能夠簡化指令譯碼的過程,但卻容易致使代碼尺寸增長。爲避免這個問題,能夠考慮採起如下措施來縮減程序ARM代碼量。

2.1 使用多寄存器操做指令

ARM 指令集中的多寄存器操做指令LDM/STM 能夠加載/ 存儲多個寄存器,這在保存/ 恢復寄存器組的狀態及進行大塊數據複製時很是有效。例如要將寄存器R4~R12 及R14 的內容保存到堆棧中,若用STR 指令共須要10 條,而一條STMEA R13!, {R4 ?? R12, R14} 指令就能達到相同的目的,節省的指令存儲空間至關可觀。不過須要注意的是, 雖然一條LDM/STM 指令能代替多條LDR/STR 指令,但這並不意味着程序運行速度獲得了ARM提升。實際上處理器在執行LDM/STM 指令的時候仍是將它拆分紅多條單獨的LDR/STR 指令來執行

2.2 合理安排變量順序

ARM 7 處理器要求ARM程序中的32 位/16 位變量必須按字/ 半字對齊,這意味着若是變量順序安排不合理, 有可能會形成存儲空間的浪費。例如:一個結構體中的4個32 位int 型變量i1 ~ i4 和4 個8 位char 型變量c1 ~ c4,若按照i一、c一、i二、c二、i三、c三、i四、c4 的順序交錯存放時, 因爲整型變量的對齊會致使位於2 個整型變量中間的那個8 位char 型變量實際佔用32 位的存儲器,這樣就形成了存儲空間的浪費。爲避免這種狀況, 應將int 型變量和char 型變量按相似i一、i二、i三、i四、c一、c二、c三、c4 的順序連續存放。

2.3 使用Thumb 指令

爲了從根本上有效ARM下降代碼尺寸,ARM 公司開發了16 位的Thumb 指令集。Thumb 是ARM 體系結構的擴充。Thumb 指令集是大多數經常使用32 位ARM 指令壓縮成16 位寬指令的集合。在執行時,16 位指令透明的實時解壓成32 位ARM 指令並無性能損失。並且程序在Thumb狀態和ARM 狀態之間切換是零開銷的。與等價的32 位ARM 代碼相比,Thumb 代碼節省的存儲器空間可高達35% 以上。

相關文章
相關標籤/搜索