李洋+張振東
摘 要:隨著嵌入式處理器性能的不斷提高,處理器性能已經(jīng)不是影響彈載計(jì)算機(jī)系統(tǒng)整體性能的主要因素。系統(tǒng)升級(jí)越來越多地注重程序的執(zhí)行效率、編譯器優(yōu)化能力、程序并行設(shè)計(jì)等方向。本文從一般性的程序運(yùn)行切入,分析了影響程序執(zhí)行效率的因素、編譯器優(yōu)化的局限性,從程序定義、減少函數(shù)調(diào)用、提高循環(huán)效率、減少不必要內(nèi)存訪問等角度介紹了一些提高程序執(zhí)行效率的程序設(shè)計(jì)方法。
關(guān)鍵詞:彈載計(jì)算機(jī);程序優(yōu)化;編譯器;代碼移動(dòng)
中圖分類號(hào):TP311 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1673-5048(2014)05-0037-04
0 引 言
在嵌入式領(lǐng)域,實(shí)時(shí)性一直是衡量系統(tǒng)性能的一項(xiàng)重要指標(biāo),它主要取決于系統(tǒng)硬件性能和軟件設(shè)計(jì)兩個(gè)方面,以往由于硬件核心處理單元性能的限制,致使整個(gè)嵌入式系統(tǒng)的運(yùn)行環(huán)境、適用范圍、代碼體積、系統(tǒng)性能受到了很大的局限。然而隨著超大規(guī)模集成電路的快速發(fā)展,德州儀器、高通、ARM等公司嵌入式處理器產(chǎn)品線的成熟和廣泛應(yīng)用,核心處理單元已經(jīng)不是影響系統(tǒng)性能的最主要因素,人們開始越來越多地關(guān)注程序的執(zhí)行效率、編譯器的優(yōu)化能力、程序并行設(shè)計(jì)等問題。
作為嵌入式應(yīng)用的一個(gè)典型代表,彈載計(jì)算機(jī)也面臨著類似問題,美國AIM-120中程空空導(dǎo)彈經(jīng)歷了數(shù)次改進(jìn),每次除了硬件性能有部分升級(jí)外,還包含大量的算法改進(jìn)、代碼優(yōu)化等軟件升級(jí)。我國空空導(dǎo)彈目前已經(jīng)發(fā)展到第四代,算法改進(jìn)、代碼優(yōu)化、并行設(shè)計(jì)也是在進(jìn)行軟件升級(jí)時(shí)所采取的有效手段。
1 影響程序運(yùn)行效率的因素
高效的計(jì)算機(jī)程序主要取決于兩方面的因素:①針對(duì)問題選擇最適宜的算法和數(shù)據(jù)結(jié)構(gòu)。一個(gè)算法的評(píng)價(jià)主要從時(shí)間復(fù)雜度和空間復(fù)雜度來考慮,對(duì)于彈載計(jì)算機(jī)寶貴的硬件資源和嚴(yán)格的強(qiáng)實(shí)時(shí)性要求而言,選擇使用的算法必須在保證實(shí)時(shí)性的前提下盡可能少地占用彈載計(jì)算機(jī)資源。數(shù)據(jù)結(jié)構(gòu)則是對(duì)算法的支撐,程序涉及的數(shù)據(jù)都屬于某種數(shù)據(jù)類型,類型明顯或隱含地規(guī)定了數(shù)據(jù)的取值范圍、存儲(chǔ)方式以及允許進(jìn)行的運(yùn)算,選擇與算法相適宜的數(shù)據(jù)結(jié)構(gòu)可以帶來更高的運(yùn)行或存儲(chǔ)效率;②有足夠智能化的編譯器,能夠充分理解程序源代碼的意圖,進(jìn)而把源代碼轉(zhuǎn)化為高效的可執(zhí)行代碼,同時(shí)能夠避免由于優(yōu)化而帶來的諸多問題。然而,就編譯器本身而言具有多方面的局限性。
2 編譯器的局限性
編譯器利用極為復(fù)雜的算法來確定程序中的變量是如何參與運(yùn)算以及被引用的,因此編譯器可以盡量簡化源程序的計(jì)算方法,比如用移位操作代替源程序中的乘法運(yùn)算。然而,編譯器對(duì)程序進(jìn)行優(yōu)化時(shí)仍受到三個(gè)方面的限制:①不能改變?cè)闯绦虻挠?jì)算結(jié)果;②編譯器的算法通常不能完全理解源程序的思想,以及變量使用的環(huán)境;③整個(gè)編譯過程的時(shí)間是可接受的。
2.1 存儲(chǔ)器別名使用
voidaliase2(intpt1,intpt2){
pt1+=2pt2;}
兩段程序似乎具有相同的功能,它們都是把指針pt2指向變量值的2倍加到指針pt1指向的變量上,但是由于aliase2進(jìn)行了3次訪問內(nèi)存的操作,而aliase1卻訪問了6次。因此,編譯器在對(duì)aliase1進(jìn)行優(yōu)化時(shí)如果按照aliase2那樣生成代碼,程序?qū)碛懈叩膱?zhí)行效率。然而,如果當(dāng)pt1和pt2同時(shí)指向同一內(nèi)存單元時(shí),那么程序aliase1將執(zhí)行如下操作:
pt1+=pt1;pt1+=pt1;
執(zhí)行后的結(jié)果是pt1指向內(nèi)存單元的內(nèi)容是執(zhí)行前的4倍,而aliase2則進(jìn)行另一種操作:
pt1+=2pt1;
很顯然,執(zhí)行后的結(jié)果是pt1指向內(nèi)存單元的內(nèi)容是執(zhí)行前的3倍。實(shí)際中,編譯器不會(huì)知道aliase1是如何被調(diào)用的、兩個(gè)參數(shù)的關(guān)系是什么,因此編譯器必須假設(shè)pt1和pt2是可能相等的,從而它不能按照aliase2那樣生成代碼。
上述這種現(xiàn)象稱為存儲(chǔ)器別名使用,編譯器必須假設(shè)程序中的不同指針在初始化后或在運(yùn)行過程中是可能指向同一地址單元的,這種假設(shè)極大地限制了編譯器對(duì)程序的優(yōu)化能力。
2.2 函數(shù)調(diào)用的副作用
限制編譯器優(yōu)化能力的另一個(gè)因素來自于函數(shù)調(diào)用,先看如下兩個(gè)函數(shù):
intfun(int);intfun1(x){
returnf(x)+f(x)+f(x)+f(x);}
兩個(gè)函數(shù)看來似乎有相同的計(jì)算結(jié)果,fun2調(diào)用fun一次,而fun1調(diào)用fun四次,對(duì)于函數(shù)的調(diào)用,計(jì)算機(jī)要進(jìn)行壓棧等一系列復(fù)雜的操作,因此fun1比fun2具有更大的開銷,在運(yùn)算結(jié)果相同的前提下,編譯器應(yīng)該按照fun2去優(yōu)化代碼,但是函數(shù)fun如果是如下的結(jié)構(gòu):
intcounter=0;intfun(intx){
returncounter++;}函數(shù)fun就改變了一個(gè)全局變量的值,全局變量的值會(huì)隨著函數(shù)fun被調(diào)用的次數(shù)不斷改變。再考慮fun1和fun2,fun1返回的值是0+1+2+3=6,而調(diào)用fun2返回的值是40=0。
這種現(xiàn)象稱為函數(shù)調(diào)用的副作用,編譯器在進(jìn)行編譯時(shí)不會(huì)去判斷一個(gè)函數(shù)的調(diào)用是否具有副作用,也不會(huì)去把原函數(shù)試著替換為另一個(gè)擁
2.3 編譯器設(shè)計(jì)的權(quán)衡
除了上述兩種情況,某些編程語言由于自身的特性,往往比其他編程語言難于優(yōu)化,例如C語言允許指針運(yùn)算和強(qiáng)制類型轉(zhuǎn)換操作,這些特性會(huì)影響編譯器對(duì)代碼的理解和優(yōu)化。同時(shí),在進(jìn)行程序優(yōu)化時(shí),通常還要考慮一些其他因素,比如程序是如何運(yùn)行,或者在可讀性與高效率之間進(jìn)行權(quán)衡等等。
由此看來,由于編譯器優(yōu)化的局限性,一個(gè)足夠智能的編譯器設(shè)計(jì)起來異常復(fù)雜,很可能需要針對(duì)目標(biāo)程序的不同應(yīng)用而專門設(shè)計(jì)。編譯器在優(yōu)化目標(biāo)代碼時(shí)很可能會(huì)改變?cè)械某绦蚪Y(jié)構(gòu)和代碼次序,因此這種編譯器在設(shè)計(jì)時(shí)也需要考慮足夠多的條件以避免優(yōu)化帶來的錯(cuò)誤,需要耗費(fèi)人們大量的時(shí)間和精力,且不能通用。從理論上來說由于過于復(fù)雜,每次編譯過程也需要相當(dāng)長的時(shí)間,因此,智能編譯器從目前來看是不切實(shí)際的想法。
換個(gè)角度考慮,由于代碼中細(xì)小的改動(dòng)都可能使編譯器對(duì)代碼的優(yōu)化起到很大的幫助,因此除了依賴編譯器對(duì)目標(biāo)程序的優(yōu)化,程序員如果在寫程序時(shí)更加注重代碼的效率,也可以提高程序執(zhí)行的效率。
3 高效的程序設(shè)計(jì)
程序運(yùn)行時(shí)間從理論上講主要取決于它的輸入規(guī)模,在實(shí)際中還受具體處理器性能、I/O、方法調(diào)用等條件的影響,當(dāng)輸入規(guī)模一定時(shí),針對(duì)后者對(duì)程序代碼做一些優(yōu)化,將提升代碼的執(zhí)行效率。
3.1 程序定義
為了說明代碼優(yōu)化對(duì)程序執(zhí)行效率的影響,這里用處理向量數(shù)據(jù)結(jié)構(gòu)的程序段為例。首先定義結(jié)構(gòu)體:
typedefstruct{
intlength;intdata;
}vec_rec,vec_ptr;
如圖1建立一個(gè)定長的向量結(jié)構(gòu),作為程序的輸入規(guī)模。定義工作函數(shù),作為優(yōu)化目標(biāo)函數(shù):voidtest1(vec_ptrv,intdest){
inti;
for(i=0;i intval; get_vec_element(v,i,&val);dest=(dest)val; }} 其中vec_length(v)用來取得向量v的長度,get_vec_element(v,i,&val)用來把向量v中第i個(gè)值賦給val,兩個(gè)函數(shù)都用C語言實(shí)現(xiàn),這里省略。 在默認(rèn)環(huán)境下,使用某型處理器,通過編譯執(zhí)行,與使用GCC編譯時(shí)“-O2”優(yōu)化選項(xiàng)相較。通過代碼分析軟件得出優(yōu)化前后兩個(gè)程序的CPE(cyclesperelement,每元素周期數(shù))分別是41.86和33.25,很明顯,如果執(zhí)行優(yōu)化編譯,可以提升代碼效率。 3.2 消除循環(huán)中的低效率 通過分析test1發(fā)現(xiàn),函數(shù)vec_length會(huì)隨著for循環(huán)的進(jìn)行每次迭代都被調(diào)用,而向量v的長度是一定的,不會(huì)隨著每次循環(huán)而改變,因此可以在循環(huán)前只計(jì)算一次向量的長度,并把它賦給一個(gè)局部變量length,由此得到改進(jìn)后的程序test2: voidtest2(vec_ptrv,intdest){ inti; intlength=vec_length(v); for(i=0;i intval; get_vec_element(v,i,&val);dest=(dest)val; } } test2的CPE是21.25,這種方法也叫代碼移動(dòng),對(duì)于代碼中一些每次計(jì)算都得到相同值的部分,可以把它提前到程序的某一行,避免重復(fù)計(jì)算帶來的開銷。鑒于之前提到的編譯器局限性,在進(jìn)行代碼移動(dòng)時(shí),編譯器不能檢測(cè)被移動(dòng)的部分是否具有函數(shù)調(diào)用的副作用,目前最復(fù)雜的編譯器依然無法對(duì)所有程序做出這種檢測(cè),因此在這種情況下只有通過程序員自己的判斷來進(jìn)行優(yōu)化。這個(gè)例子很好地證明了在程序中看似簡單的部分通常隱藏著與輸入規(guī)模成正比的低效率。 3.3 減少函數(shù)調(diào)用 函數(shù)調(diào)用時(shí)涉及壓棧等一系列操作,隱含著通過上述方法,程序的執(zhí)行效率得到了提高,當(dāng)然這是在輸入規(guī)模達(dá)到一定程度的情況下得到的結(jié)果,特別是對(duì)某些計(jì)算密集型程序而言。然而,在實(shí)際中并不是所有程序都擁有這種模型,這就需要其他的專業(yè)知識(shí),比如對(duì)所用處理器的特性有深層次的理解,熟練掌握對(duì)應(yīng)的匯編語言,從而對(duì)編譯器如何生成、執(zhí)行代碼有充分的認(rèn)識(shí),在進(jìn)行程序優(yōu)化時(shí)才能更好地去實(shí)現(xiàn)最初的預(yù)想。
換個(gè)角度考慮,由于代碼中細(xì)小的改動(dòng)都可能使編譯器對(duì)代碼的優(yōu)化起到很大的幫助,因此除了依賴編譯器對(duì)目標(biāo)程序的優(yōu)化,程序員如果在寫程序時(shí)更加注重代碼的效率,也可以提高程序執(zhí)行的效率。
3 高效的程序設(shè)計(jì)
程序運(yùn)行時(shí)間從理論上講主要取決于它的輸入規(guī)模,在實(shí)際中還受具體處理器性能、I/O、方法調(diào)用等條件的影響,當(dāng)輸入規(guī)模一定時(shí),針對(duì)后者對(duì)程序代碼做一些優(yōu)化,將提升代碼的執(zhí)行效率。
3.1 程序定義
為了說明代碼優(yōu)化對(duì)程序執(zhí)行效率的影響,這里用處理向量數(shù)據(jù)結(jié)構(gòu)的程序段為例。首先定義結(jié)構(gòu)體:
typedefstruct{
intlength;intdata;
}vec_rec,vec_ptr;
如圖1建立一個(gè)定長的向量結(jié)構(gòu),作為程序的輸入規(guī)模。定義工作函數(shù),作為優(yōu)化目標(biāo)函數(shù):voidtest1(vec_ptrv,intdest){
inti;
for(i=0;i intval; get_vec_element(v,i,&val);dest=(dest)val; }} 其中vec_length(v)用來取得向量v的長度,get_vec_element(v,i,&val)用來把向量v中第i個(gè)值賦給val,兩個(gè)函數(shù)都用C語言實(shí)現(xiàn),這里省略。 在默認(rèn)環(huán)境下,使用某型處理器,通過編譯執(zhí)行,與使用GCC編譯時(shí)“-O2”優(yōu)化選項(xiàng)相較。通過代碼分析軟件得出優(yōu)化前后兩個(gè)程序的CPE(cyclesperelement,每元素周期數(shù))分別是41.86和33.25,很明顯,如果執(zhí)行優(yōu)化編譯,可以提升代碼效率。 3.2 消除循環(huán)中的低效率 通過分析test1發(fā)現(xiàn),函數(shù)vec_length會(huì)隨著for循環(huán)的進(jìn)行每次迭代都被調(diào)用,而向量v的長度是一定的,不會(huì)隨著每次循環(huán)而改變,因此可以在循環(huán)前只計(jì)算一次向量的長度,并把它賦給一個(gè)局部變量length,由此得到改進(jìn)后的程序test2: voidtest2(vec_ptrv,intdest){ inti; intlength=vec_length(v); for(i=0;i intval; get_vec_element(v,i,&val);dest=(dest)val; } } test2的CPE是21.25,這種方法也叫代碼移動(dòng),對(duì)于代碼中一些每次計(jì)算都得到相同值的部分,可以把它提前到程序的某一行,避免重復(fù)計(jì)算帶來的開銷。鑒于之前提到的編譯器局限性,在進(jìn)行代碼移動(dòng)時(shí),編譯器不能檢測(cè)被移動(dòng)的部分是否具有函數(shù)調(diào)用的副作用,目前最復(fù)雜的編譯器依然無法對(duì)所有程序做出這種檢測(cè),因此在這種情況下只有通過程序員自己的判斷來進(jìn)行優(yōu)化。這個(gè)例子很好地證明了在程序中看似簡單的部分通常隱藏著與輸入規(guī)模成正比的低效率。 3.3 減少函數(shù)調(diào)用 函數(shù)調(diào)用時(shí)涉及壓棧等一系列操作,隱含著通過上述方法,程序的執(zhí)行效率得到了提高,當(dāng)然這是在輸入規(guī)模達(dá)到一定程度的情況下得到的結(jié)果,特別是對(duì)某些計(jì)算密集型程序而言。然而,在實(shí)際中并不是所有程序都擁有這種模型,這就需要其他的專業(yè)知識(shí),比如對(duì)所用處理器的特性有深層次的理解,熟練掌握對(duì)應(yīng)的匯編語言,從而對(duì)編譯器如何生成、執(zhí)行代碼有充分的認(rèn)識(shí),在進(jìn)行程序優(yōu)化時(shí)才能更好地去實(shí)現(xiàn)最初的預(yù)想。
換個(gè)角度考慮,由于代碼中細(xì)小的改動(dòng)都可能使編譯器對(duì)代碼的優(yōu)化起到很大的幫助,因此除了依賴編譯器對(duì)目標(biāo)程序的優(yōu)化,程序員如果在寫程序時(shí)更加注重代碼的效率,也可以提高程序執(zhí)行的效率。
3 高效的程序設(shè)計(jì)
程序運(yùn)行時(shí)間從理論上講主要取決于它的輸入規(guī)模,在實(shí)際中還受具體處理器性能、I/O、方法調(diào)用等條件的影響,當(dāng)輸入規(guī)模一定時(shí),針對(duì)后者對(duì)程序代碼做一些優(yōu)化,將提升代碼的執(zhí)行效率。
3.1 程序定義
為了說明代碼優(yōu)化對(duì)程序執(zhí)行效率的影響,這里用處理向量數(shù)據(jù)結(jié)構(gòu)的程序段為例。首先定義結(jié)構(gòu)體:
typedefstruct{
intlength;intdata;
}vec_rec,vec_ptr;
如圖1建立一個(gè)定長的向量結(jié)構(gòu),作為程序的輸入規(guī)模。定義工作函數(shù),作為優(yōu)化目標(biāo)函數(shù):voidtest1(vec_ptrv,intdest){
inti;
for(i=0;i intval; get_vec_element(v,i,&val);dest=(dest)val; }} 其中vec_length(v)用來取得向量v的長度,get_vec_element(v,i,&val)用來把向量v中第i個(gè)值賦給val,兩個(gè)函數(shù)都用C語言實(shí)現(xiàn),這里省略。 在默認(rèn)環(huán)境下,使用某型處理器,通過編譯執(zhí)行,與使用GCC編譯時(shí)“-O2”優(yōu)化選項(xiàng)相較。通過代碼分析軟件得出優(yōu)化前后兩個(gè)程序的CPE(cyclesperelement,每元素周期數(shù))分別是41.86和33.25,很明顯,如果執(zhí)行優(yōu)化編譯,可以提升代碼效率。 3.2 消除循環(huán)中的低效率 通過分析test1發(fā)現(xiàn),函數(shù)vec_length會(huì)隨著for循環(huán)的進(jìn)行每次迭代都被調(diào)用,而向量v的長度是一定的,不會(huì)隨著每次循環(huán)而改變,因此可以在循環(huán)前只計(jì)算一次向量的長度,并把它賦給一個(gè)局部變量length,由此得到改進(jìn)后的程序test2: voidtest2(vec_ptrv,intdest){ inti; intlength=vec_length(v); for(i=0;i intval; get_vec_element(v,i,&val);dest=(dest)val; } } test2的CPE是21.25,這種方法也叫代碼移動(dòng),對(duì)于代碼中一些每次計(jì)算都得到相同值的部分,可以把它提前到程序的某一行,避免重復(fù)計(jì)算帶來的開銷。鑒于之前提到的編譯器局限性,在進(jìn)行代碼移動(dòng)時(shí),編譯器不能檢測(cè)被移動(dòng)的部分是否具有函數(shù)調(diào)用的副作用,目前最復(fù)雜的編譯器依然無法對(duì)所有程序做出這種檢測(cè),因此在這種情況下只有通過程序員自己的判斷來進(jìn)行優(yōu)化。這個(gè)例子很好地證明了在程序中看似簡單的部分通常隱藏著與輸入規(guī)模成正比的低效率。 3.3 減少函數(shù)調(diào)用 函數(shù)調(diào)用時(shí)涉及壓棧等一系列操作,隱含著通過上述方法,程序的執(zhí)行效率得到了提高,當(dāng)然這是在輸入規(guī)模達(dá)到一定程度的情況下得到的結(jié)果,特別是對(duì)某些計(jì)算密集型程序而言。然而,在實(shí)際中并不是所有程序都擁有這種模型,這就需要其他的專業(yè)知識(shí),比如對(duì)所用處理器的特性有深層次的理解,熟練掌握對(duì)應(yīng)的匯編語言,從而對(duì)編譯器如何生成、執(zhí)行代碼有充分的認(rèn)識(shí),在進(jìn)行程序優(yōu)化時(shí)才能更好地去實(shí)現(xiàn)最初的預(yù)想。