張喜俊,唐云凱
(1.中國電子科技集團公司 第四十一研究所,青島266555; 2.西北核技術研究所)
張喜俊(工程師),研究方向為測試測量儀器軟件。
C編譯器是嵌入式系統程序員的基本工具,正是它將程序員的思想和算法轉換成處理器可以執行的機器碼。所有的C編譯器都能夠執行各種類型的優化。以gcc編譯器為例,除了常見的-O1、-O2、-O3優化選項以外,還可以根據需要打開其他優化開關,它們的含義如表1所列。

表1 gcc編譯器優化選項含義
在編碼過程中,程序員還應該盡可能地為編譯器提供更多的信息,協助編譯器更好地進行優化。程序員與編譯器主要通過C語言關鍵字進行交流。以C標準庫函數strcpy為例,它的原型為“char*strcpy(char*dst,const char*src);” ,而不是“char*strcpy(char*dst,char*src);”。雖然它們只是相差一個const關鍵字,但在編譯器看來卻相差甚遠。如果使用const修飾src,表明src指向一個常量字符串,編譯器可直接從寄存器或者高速緩存中訪問它們,而不必每次都從存儲器中讀出它們的內容。這樣不僅提高了執行速度,且編譯器也可捕獲對src指向的字符串的修改,并提示程序員不應該修改常量字符串。
由于現代編譯器已經能夠出色地自動完成大多數優化工作,因此切忌盲目手動優化。在進行實際的優化之前,可以借助代碼剖析工具進行代碼分析,找出關鍵代碼片段,然后再進行手動優化。使用gcov或者gprof之類的剖析器,可以找出一些有關代碼的基本性能統計數據,例如每行代碼的調用次數、代碼覆蓋率和各部分代碼耗費的CPU時間等。了解這些基本信息之后,就知道了應該對哪些代碼進行優化。
計算密集型應用指的是那些進行大量計算的應用,特別是與數字信號處理相關的應用。算法和數據結構的選擇至關重要,它們應該是優化工作的重點。除了算法優化,在編碼過程中程序員還應該盡可能地對基本運算進行優化。例如:
①如果b>0并且b×c不會飽和,那么可以使用乘法代替除法,將(a/b)>c替換為a>(c×b)。
②如果乘法運算中的乘數或者除法運算中的分母為2的倍數,那么可以使用移位操作代替乘除法。
③加法要快于乘法,如可使用(a+a+a)替換a×3。
以空間換效率,即以犧牲代碼尺寸為代價換取運行效率。常見的優化策略包括循環展開、查找表、數組索引和內聯函數等。
編者注:有關循環展開、查找表等內容的詳細介紹請見本刊網站www.mesnet.com.cn。
2.3.1盡可能少使用全局變量
由于全局變量是全局可見的,可以在多個地方對其進行修改,因此編譯器不能在寄存器中緩存全局變量的值。這樣,讀或者寫全局全局變量時,都必須訪問存儲器以裝載或存儲它們;而且在訪問全局變量時,為了保證其完整性必須確保操作是原子的,這又會增加開銷。
2.3.2減小函數參數傳遞的開銷
以ARM處理器為例,如果函數只包含4個參數或更少,每個參數的大小為一個字或更小,那么可以通過寄存器R0~R3來傳遞所有的參數。相反,如果參數個數大于4,那么其他的參數只能通過位于片內 RAM或者速度更慢的SDRAM中的堆棧來傳遞,與寄存器相比,它們的訪問速度要慢很多。注意,在C++中非靜態成員總是有一個this指針參數,一般通過寄存器R0傳遞,這樣只剩下R1~R3三個寄存器可以傳遞其他參數。
如果某個函數參數為一個大型的結構體,那么最好將其改為傳遞指向這個結構體的指針。這是因為,C語言中的函數調用默認情況下是傳值調用,編譯器會將結構體復制到堆棧。如果傳遞一個結構體指針,那么只需要復制這個4字節的指針到寄存器。
2.3.3合理安排運行時代碼和數據的位置
嵌入式系統中往往存在多種類型的存儲器(如片內Flash、片內 SRAM 以及擴展的 SDRAM、Flash和EEPROM等),它們的訪問速度相差甚遠。同時,應用程序代碼不同部分的執行頻率也相差很大。以ARM嵌入式系統為例,異常向量表和中斷處理器的執行最為頻繁。為了獲得最佳性能,可以將它們復制到片內SRAM中,然后執行存儲器重映射將異常向量表定位到0地址。其他關鍵代碼片段也可以利用分散加載描述文件(用于RealView工具鏈,gnu工具鏈使用.lds文件)通知鏈接器和加載器,將它們裝載到指定的存儲器地址。
優秀的代碼傾向于展示良好的局部性,它包含兩個方面的含義:其一,傾向于在極短的時間間隔內,多次引用同一存儲器位置;其二,傾向于在極短的時間間隔內,引用當前訪問的數據項附近的數據項。
具有良好局部性的代碼之所以具有較高的運行效率,這是因為計算機的存儲器系統是一個具有不同容量、成本和訪問時間的存儲設備的層次結構。嵌入式系統的存儲器層次結構一般可以分為寄存器、高速緩存、主存和Flash,它們的訪問時間逐步上升。層次結構中的每一層都緩存來自較低一層的數據對象,如果需要訪問的數據沒有位于高速緩存中(即緩存不命中),那么不得不耗費數十倍的時間從主存中獲得,極大地降低了運行效率。
比較下面2種二維數組求和方法。由于C語言以行優先順序存儲數組,因此行優先累加方法的局部性要優于列優先累加方法。


[1]Sloss Andrew N,Symes Dominic,Wright Chris.ARM嵌入式系統開發:軟件設計與優化[M].沈建華,譯.北京:北京航空航天大學出版社,2005.
[2]Bryant Randal E,O'Hallaron David.深入理解計算機系統(修訂版)[M].龔奕利,雷迎春,譯.北京:中國電力出版社,2004.
[3]M Richard.Using the GNU Compiler Collection:For GCC version 4.4.1[EB/OL].[2009-10].http://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc.pdf.
[4]Ghosh Koushik.Writing Efficient C andC Code Optimization[EB/OL].(2004-2-26)[2009-10].http://www.codeproject.com/KB/cpp/C_Code_Optimization.aspx.