針對嵌入式SoC應用的C編程優化
開發運行在SoC內的嵌入式處理器內核的程序時,工程師有兩個主要目的:運行得足夠快,使處理器運行的頻率降到最低;消耗盡量少的內存,使內存開銷降到最小。
本文引用地址:http://www.j9360.com/article/190436.htm對于不同的項目,有時候這兩個因素的重要性會不一樣。下面兩個關鍵因素極大地影響著設計團隊滿足這些目標的能力:開發源程序的編譯器對代碼的優化效率以及用于開發源代碼的編程風格。本文將深入地討論這兩種因素,并提出一些創建小而快的C程序的建議。
編譯器原理
編譯器通常是由前端和后端兩部分組成。前端通常是指語法和語義的處理過程,后端通常是指優化、代碼生成,以及針對特定處理器的優化過程。很多好的編譯器后端依賴于多層的中間表述(IR)。優化和代碼生成從高層(類型輸入程序的句法)到低層逐級地傳遞中間表述。與處理器無關的優化一般傾向于在編譯過程的早期在較高IR層上實現,而針對特定處理器的優化一般傾向于在編譯過程的后期在低層IR上來實現。信息通過不同IR層向下傳遞,這樣低層優化可以充分利用編譯器早期處理得到的高層信息。
Tensilica針對其Xtensa可配置處理器和Diamond標準處理器的XCC/C++編譯器包含四個基本的優化級,從-O0到-O3,對應著不斷提高的優化級別。表1描述了這些級別及其相對應的代碼大小和內部過程分析(IPA)。缺省情況下,XCC編譯器一次優化一個文件,但是它也可以執行內部過程分析(通過加入IPA的編譯選項)。當在多個原文件上優化整個應用程序時,優化將會被延遲到鏈接的步驟之后進行。表2描述了當前編譯器(包括 XCC編譯器)支持的優化內容部分列表。
XCC編譯器還可以利用編譯產生的性能分析數據。性能分析的反饋可以幫助編譯器減輕分支跳轉的延遲。另外,反饋可以讓編譯器只是插入那些最常用的函數(inline),并且妥善處理常用代碼段中寄存器溢出的問題。因此,性能分析反饋允許XCC編譯器在所有地方進行正常優化的同時,還可以通過優化應用中的臨界部分進行加速。
一些有用的C編碼規則
為了利用編譯器得到最好的性能,編程人員需要像編譯器一樣思考問題,并且理解C語言和目標處理器之間的關系。下面的一些基本原則可以幫助所有嵌入式編程人員在不需很大努力的情況下獲得性能好很多的編譯代碼。
1. 觀察編譯得到的代碼
完全理解編譯器對全部代碼如何編譯是不可能的。如果XCC編譯器設置了—S或者-save-temps編譯選項,編譯將產生匯編輸出并且還有一些為了理解而添加的注釋。對于那些性能要求很高的代碼,你可以觀察編譯結果是否符合你的期望。如果不是,請考慮以下規則。
2. 了解混淆發生的情況
C語言允許任意地使用指針,這增加了混淆出現的機會,這允許程序用很多種方法去引用同一數據對象。如果全局變量的地址被作為子程序的參數傳遞,這個變量可以通過它的名字或者通過指針被引用。這就是一種混淆,編譯器必須保守地把這樣的數據對象保存在內存中而不是寄存器中,并且仔細地保持代碼中可能引起混淆的變量的訪問順序。考慮下面的代碼:
void foo(int *a, int *b)
{
int i;
for (i=0; i100; i++) {
*a += b[i];
}
}
您會設想編譯器應該產生代碼是在循環開始前將*a保存到一個寄存器里面,并且在循環中把b[i]保存到一個寄存器里面然后將它加到*a所在的寄存器里。但事實上卻是,編譯器產生的結果是*a被放置在內存里面,因為a和b可以產生混淆情況,*a也許是b數組的一個元素。雖然看起來在這個例子中不太可能出現這種混淆,但是編譯器是沒法確定這種情況是否會發生的。有幾個技巧可以針對混淆的情況,幫助編譯器能做到更好的編譯工作:你可以使用-IPA 編譯選項進行編譯,你可以用全局變量代替參數,你可以使用特殊編譯選項進行編譯,或者可以在聲明變量中使用_restrict屬性。
3. 指針常常引起混淆
編譯器識別指針指向的目標對象經常會遇到問題。程序員可以通過使用本地變量幫助編譯器去避免混淆,具體方法是使用本地變量去存儲依據指針訪問獲得的值,因為不直接的操作和調用影響指針引用的值而不是本地變量的值。因此,編譯器會把本地變量放到寄存器里面去。
下面的例子顯示如何正確使用指針以避免混淆從而產生更好的編譯代碼。在這個例子里面,優化者不知道*p++=0是否會修改len,所以它不能把len放到寄存器里面去獲得性能提升。相反每個循環中,len都被放到了內存里面。
int len = 10;
void
zero(char *p)
{
int i;
for (i=0; i
}
通過使用本地變量而不是全局變量,可以避免混淆。
int len = 10;
void
zero(char *p)
{
int local_len = len;
int i;
for (i=0; i local_len; i++) *p++ = 0;
}
4. 使用const和restrict限定詞
_restrict限定詞告訴編譯器可以假設有資格的指針是唯一訪問某內存或數據對象的方式。通過這個指針的Load和Store操作不會引起與這個函數內部其它Load和Store操作的混淆,除非通過這個指針的訪問。例如:
float x[ARRAY_SIZE];
float *c = x;
void f4_opt(int n, float * __restrict a, float * __restrict b)
{
int i;
/* No data dependence across iterations because of __restrict */
for (i = 0; i n; i++)
a[i] = b[i] + c[i];
}
linux操作系統文章專題:linux操作系統詳解(linux不再難懂)
評論