1. 程式人生 > >[讀書筆記3]《C語言嵌入式系統程式設計修煉》

[讀書筆記3]《C語言嵌入式系統程式設計修煉》

第五章 效能優化

 

5.1 使用巨集定義

  在C語言中,巨集是產生內嵌程式碼的唯一方法。對於嵌入式系統而言,為了能達到效能要求,巨集是一種很好的代替函式的方法。
 
  寫一個"標準"巨集MIN ,這個巨集輸入兩個引數並返回較小的一個:
  錯誤做法:

#define MIN(A,B)  ( A <= B ? A : B )

  正確做法:

#define MIN(A,B) ((A)<= (B) ? (A) : (B) )

 
  對於巨集,我們需要知道三點:

  • (1)巨集定義"像"函式;
  • (2)巨集定義不是函式,因而需要括上所有"引數";
  • (3)巨集定義可能產生副作用。
     
      下面的程式碼:
least = MIN(*p++, b);

  將被替換為:

( (*p++) <= (b) ?(*p++):(b) )

  發生的事情無法預料。 因而不要給巨集定義傳入有副作用的"引數"。

5.2 使用暫存器變數

  當對一個變數頻繁被讀寫時,需要反覆訪問記憶體,從而花費大量的存取時間。為此,C語言提供了一種變數,即暫存器變數。這種變數存放在CPU的暫存器中,使用時,不需要訪問記憶體,而直接從暫存器中讀寫,從而提高效率。暫存器變數的說明符是register。對於迴圈次數較多的迴圈控制變數及迴圈體內反覆使用的變數均可定義為暫存器變數,而迴圈計數是應用暫存器變數的最好候選者。

  • (1) 只有區域性自動變數和形參才可以定義為暫存器變數。因為暫存器變數屬於動態儲存方式,凡需要採用靜態儲存方式的量都不能定義為暫存器變數,包括:模組間全域性變數、模組內全域性變數、區域性static變數;
  • (2) register是一個"建議"型關鍵字,意指程式建議該變數放在暫存器中,但最終該變數可能因為條件不滿足並未成為暫存器變數,而是被放在了儲存器中,但編譯器中並不報錯(在C++語言中有另一個"建議"型關鍵字:inline)。
     
      下面是一個採用暫存器變數的例子:
/* 求1+2+3+….+n的值 */
WORD Addition(BYTE n)
{
 register i,s=0;
 for(i=1;i<=n;i++)
 {
  s=s+i;
 }
 return s;
}

  本程式迴圈n次,i和s都被頻繁使用,因此可定義為暫存器變數。

5.3 內嵌彙編

  程式中對時間要求苛刻的部分可以用內嵌彙編來重寫,以帶來速度上的顯著提高。但是,開發和測試彙編程式碼是一件辛苦的工作,它將花費更長的時間,因而要慎重選擇要用匯編的部分。
 
  在程式中,存在一個80-20原則,即20%的程式消耗了80%的執行時間,因而我們要改進效率,最主要是考慮改進那20%的程式碼。
 
  嵌入式C程式中主要使用線上彙編,即在C程式中直接插入_asm{ }內嵌彙編語句:

/* 把兩個輸入引數的值相加,結果存放到另外一個全域性變數中 */
int result; 
void Add(long a, long *b) 
{ 
 _asm 
 { 
  MOV AX, a 
  MOV BX, b 
  ADD AX, [BX]
  MOV result, AX
 } 
}


5.4 利用硬體特性

  首先要明白CPU對各種儲存器的訪問速度,基本上是:CPU內部RAM > 外部同步RAM > 外部非同步RAM > FLASH/ROM
 
  對於程式程式碼,已經被燒錄在FLASH或ROM中,我們可以讓CPU直接從其中讀取程式碼執行,但通常這不是一個好辦法,我們最好在系統啟動後將FLASH或ROM中的目的碼拷貝入RAM中後再執行以提高取指令速度;
 
  對於UART等裝置,其內部有一定容量的接收BUFFER,我們應儘量在BUFFER被佔滿後再向CPU提出中斷。例如計算機終端在向目標機通過RS-232傳遞資料時,不宜設定UART只接收到一個BYTE就向CPU提中斷,從而無謂浪費中斷處理時間;
 
  如果對某裝置能採取DMA方式讀取,就採用DMA讀取,DMA讀取方式在讀取目標中包含的儲存資訊較大時效率較高,其資料傳輸的基本單位是塊,而所傳輸的資料是從裝置直接送入記憶體的(或者相反)。DMA方式較之中斷驅動方式,減少了CPU 對外設的干預,進一步提高了CPU與外設的並行操作程度。
<br >

5.5 活用位操作

  使用C語言的位操作可以減少除法和取模的運算。在計算機程式中資料的位是可以操作的最小資料單位,理論上可以用"位運算"來完成所有的運算和操作,因而,靈活的位操作可以有效地提高程式執行的效率。舉例如下:

/* 方法1 */
int i,j;
i = 879 / 16;
j = 562 % 32; 
/* 方法2 */
int i,j;
i = 879 >> 4;
j = 562 - (562 >> 5 << 5);

  對於以2的指數次方為"*"、"/"或"%"因子的數學運算,轉化為移位運算"<< >>"通常可以提高演算法效率。因為乘除運算指令週期通常比移位運算大。
 
  C語言位運算除了可以提高運算效率外,在嵌入式系統的程式設計中,它的另一個最典型的應用,而且十分廣泛地正在被使用著的是位間的與(&)、或(|)、非(~)操作,這跟嵌入式系統的程式設計特點有很大關係。我們通常要對硬體暫存器進行位設定,譬如,我們通過將AM186ER型80186處理器的中斷遮蔽控制暫存器的第低6位設定為0(開中斷2),最通用的做法是:

#define INT_I2_MASK 0x0040 
wTemp = inword(INT_MASK);
outword(INT_MASK, wTemp &~INT_I2_MASK);

 
  而將該位設定為1的做法是:

#define INT_I2_MASK 0x0040 
wTemp = inword(INT_MASK);
outword(INT_MASK, wTemp | INT_I2_MASK);

  判斷該位是否為1的做法是:

#define INT_I2_MASK 0x0040 
wTemp = inword(INT_MASK);
if(wTemp & INT_I2_MASK)
{
… /* 該位為1 */
}

 
  上述方法在嵌入式系統的程式設計中是非常常見的,我們需要牢固掌握。

5.6 總結

  在效能優化方面永遠注意80-20準備,不要優化程式中開銷不大的那80%,這是勞而無功的。
 
  巨集定義是C語言中實現類似函式功能而又不具函式呼叫和返回開銷的較好方法,但巨集在本質上不是函式,因而要防止巨集展開後出現不可預料的結果,對巨集的定義和使用要慎而處之。很遺憾,標準C至今沒有包括C++中inline函式的功能,inline函式兼具無呼叫開銷和安全的優點。
 
  使用暫存器變數、內嵌彙編和活用位操作也是提高程式效率的有效方法。
 
  除了程式設計上的技巧外,為提高系統的執行效率,我們通常也需要最大可能地利用各種硬體裝置自身的特點來減小其運轉開銷,例如減小中斷次數、利用DMA傳輸方式等。