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