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