1 引言
DSP(Digital Signal Processor,數(shù)字信號處理器)是一種具有特殊結(jié)構(gòu)的微處理器。自20世紀80年代初誕生以來,DSP在短短的十多年間里得到了飛速的發(fā)展。隨著DSP性能價格比和開發(fā)手段的不斷提高,DSP已經(jīng)在通信和信息系統(tǒng)、信號與信號處理、自動控制、雷達、軍事、航空航天、醫(yī)療、家用電器等許多領(lǐng)域得到了廣泛的應用。
與單片機相比,DSP多用于算法比較復雜、乘加運算量比較大的應用,如通信、雷達、音視頻處理等。為了追求代碼的高效,過去一般用匯編語言來編制DSP程序。隨著DSP應用范圍不斷延伸,應用的日趨復雜,匯編語言程序在可讀性、可修改性、可移植性和可重用性的缺點日益突出,軟件需求與軟件生產(chǎn)力之間的矛盾日益嚴重。引入語言(如C語言、C++、Java),可以解決該矛盾。在語言中,C語言無疑是效、靈活的。各個DSP芯片公司都相繼推出了相應的C語言編譯器。
鑒于DSP應用的復雜度,在用C語言進行DSP軟件開發(fā)時,一般先在基于通用微處理器的PC機或工作站上對算法進行仿真,仿真通過后再將C程序移植到DSP平臺中。
按照軟件開發(fā)的順序,相應的優(yōu)化工作包括兩個部分:一是仿真環(huán)境中的優(yōu)化,二是DSP目標環(huán)境中的進一步優(yōu)化。本文主要探討的是前者,給出了在DSP開發(fā)環(huán)境下有效C語言編程的策略,以獲得效的編譯代碼;并針對策略,設計了具體實例,并比較了不同策略在TMS320C54x CCS(v1.2)和TMS320C6000 CCS(v1.2)環(huán)境下編譯的結(jié)果。
2 DSP開發(fā)環(huán)境下有效C編程的策略
基于通用微處理器的PC機環(huán)境中的優(yōu)化工作是針對C程序的通用特性來考慮的。這方面的優(yōu)化工作主要包括數(shù)據(jù)類型選擇、數(shù)值操作優(yōu)化、快速算法[6]、變量定義和使用優(yōu)化、函數(shù)調(diào)用優(yōu)化、程序流程優(yōu)化以及計算表格化[6]等。
2.1 數(shù)據(jù)類型
標準C語言提供了豐富的數(shù)據(jù)類型整型、浮點、枚舉、指針、結(jié)構(gòu)、聯(lián)合等。編程面對的問題是使用怎樣的數(shù)據(jù)類型使編譯生成的代碼小、效率高。
整型有signed和unsigned之分,分別稱為char,short int,int,long int,enum。ANSI C沒有規(guī)定每個類型的大小,它只是聲明short int不大于int,long不小于int,enum和int具有相同尺度。這種模糊的定義影響了程序由一個處理器向另一個處理器的移植操作。為了避免這種影響,比較良好的編程風格是將數(shù)據(jù)類型按類型定義(typedef)在一個頭文件中,當移植時只需要更改頭文件即可:
使用浮點數(shù)是非常危險的,除非系統(tǒng)有浮點協(xié)處理器或?qū)iT針對浮點設計的處理器,使用浮點變量會使編碼尺度膨脹。即使使用協(xié)處理器,消耗時間也很多。同時,浮點數(shù)的存儲空間是可變的。IEEE單精度浮點需4B,雙精度需8B,擴展雙精度需10B。一些小的CPU的交叉編譯器僅支持單精度浮點型。
避免浮點操作的方法一般是采用浮點運算定點化,用定點函數(shù)運算替代浮點操作。對于定點DSP的操作,應仔細考慮硬件的限制。同時,在編程時要大致估計數(shù)據(jù)的范圍,做到所采用的數(shù)據(jù)類型剛好滿足要求,并盡量做到在一個CPU指令周期內(nèi)完成數(shù)據(jù)載入。
由于DSP常采用可變的定位方式,結(jié)構(gòu)的不適當聲明會浪費很多RAM和ROM空間。如左圖示例中兩種不同聲明方式,用sizeof()分析,在C54x中分別為14和12(單位為字[4]),在C6201中分別為56個字節(jié)和40個字節(jié),可見不適當?shù)芈暶鲿е聝?nèi)存空間的浪費。
當然,一些高性能的編譯器,可根據(jù)內(nèi)存空間優(yōu)化各個變量的位置,但此時變量存儲的次序可能和它們定義時的次序不同。
2.2 數(shù)值操作優(yōu)化
對數(shù)值操作優(yōu)化,主要特別注意以下幾點:
(1) 用比特的移位操作來代替2次冪整數(shù)的乘除法運算更為有效;
(2) 用查表法代替三角函數(shù)運算。特別是在FFT等程序中,同時將一些運行時計算的參數(shù)做成查找表或常數(shù)數(shù)值,這樣可以將運行時的計算轉(zhuǎn)化為編譯時的計算,從而提高運算效率;
(3) 當使用浮點設備時,盡量使用浮點數(shù)據(jù)類型,這能夠減小定點處理單元的負擔;
(4)盡量避免數(shù)值的上下溢出,除非是算法本身的需要。
2.3 變量定義及使用優(yōu)化
C語言把局部變量放在堆棧中,這種訪問是間接的,因此較慢。更為有效的方法是將變量放在堆(heap)中,有兩種方法實現(xiàn):一種是聲明為全局變量;另一種是聲明變量為static。同時,要注意提高全局變量的重復利用率。
對于需要多次重復訪問的變量,如for循環(huán)中的變量值,一般可以設置為register變量。聲明變量為register能夠提高效率,但必須小心使用。在某些編譯器中,優(yōu)化器會自動分配一些變量為register。
在C語言程序中指針和數(shù)組是可以相互替代的。對數(shù)組的尋址是非常耗時的,特別是多維數(shù)組。因此,首先應降低數(shù)組的維數(shù),再指針化。同時,配合DSP中尋址機構(gòu)所支持的增量尋址,效率會大大提高,代價是降低了可讀性。
2.4 函數(shù)調(diào)用
函數(shù)調(diào)用往往產(chǎn)生大量代碼。當C調(diào)用一個函數(shù)時,它首先把參數(shù)傳遞給寄存器或堆棧。如果函數(shù)參數(shù)很多,則調(diào)用開銷將很大。此外,還需大量堆棧空間。壞的情況是函數(shù)參數(shù)傳遞的是結(jié)構(gòu),編譯器在調(diào)用函數(shù)時必須首先復制整個結(jié)構(gòu)到堆棧。此外,若函數(shù)返回的是結(jié)構(gòu),調(diào)用程序保留堆棧空間,傳遞結(jié)構(gòu)地址給函數(shù),調(diào)用函數(shù),然后函數(shù)返回。,調(diào)用程序還要清除堆棧,并將返回的結(jié)構(gòu)復制到另一個結(jié)構(gòu)。代碼和堆棧的開銷將是驚人的,特別是資源有限的DSP或其它片上嵌入式開發(fā)系統(tǒng)。為了避免這種開銷,應禁止傳遞結(jié)構(gòu),一般用結(jié)構(gòu)指針替代。如果結(jié)構(gòu)是不可修改的,可用常量結(jié)構(gòu)指針替代。
函數(shù)調(diào)用的另一方面開銷是局部變量。這些變量定位于堆棧,因此增加了對堆棧的要求。如果這些變量需被初始化,則在程序每次被調(diào)用時均需做一次初始化。可以這樣說,限制局部變量的數(shù)目也就是對堆棧空間的限制。
第三方面的開銷是調(diào)用函數(shù)的返回值。如果要返回值,一般需要在函數(shù)返回前復制返回值到返回位置,然后把結(jié)果復制到調(diào)用程序中。如果函數(shù)的返回值賦值給一個變量或很少使用,可以考慮傳遞指向返回值的指針。被調(diào)用的函數(shù)可以直接改變返回值。
對于用C++開發(fā)的用戶,采用inline技術(shù)可以完全消除函數(shù)調(diào)用的開銷,然而這增加了目標代碼的大小。在這種情況下,應根據(jù)實際采用的編譯器判斷優(yōu)化后程序生成的代碼是否增加不大。
2.5 程序流程設計
在C語言中,程序流程控制有if…else,switch…case,do…while,for,while等,它們的使用不當也會影響程序生成代碼的大小和效率。下面,本文將分別分析使用判斷選取控制語句和循環(huán)控制語句時應該注意的事項:
在使用判斷選取控制語句時應減少判斷轉(zhuǎn)移。DSP多采用流水線結(jié)構(gòu)。如TMS320C54X中就采用了6級流水線結(jié)構(gòu),頻繁的轉(zhuǎn)移指令將使流水線難以發(fā)揮作用。另外DSP的大多數(shù)指令為單周期指令,但轉(zhuǎn)移類指令卻通常要耗費較多的機器周期。因此,應盡可能減少程序中的轉(zhuǎn)移分支。一般通過對程序流的分析,許多判斷轉(zhuǎn)移可以用簡單的條件組合來實現(xiàn)。
當有多種選擇時,switch…case語句可讀性強,然而它會帶來很大開銷,if…else語句更靈活,但它需要更多的C代碼。if…else語句在實際的編譯中可能會更為有效一些。另外一個需要考慮的是switch…case語句中參數(shù)可以是任意的整數(shù)類型,然而,若這些整數(shù)在case語句中生成一系列整數(shù),如enum類型,許多編譯器將產(chǎn)生跳轉(zhuǎn)表,這可以減少編譯代碼,而且平均下來,執(zhí)行的效率也比較快。
C語言提供3種類型的循環(huán)結(jié)構(gòu):for循環(huán)、do-while循環(huán)和while循環(huán)。編譯器的任務是將指令映射為處理器的指令集。許多DSP設計有零開銷循環(huán)處理能力。零開銷循環(huán)不需要循環(huán)計數(shù)更新、測試和回跳指令,因此能夠加速處理能力。
為了盡量實現(xiàn)循環(huán)的零開銷,編譯器必須知道循環(huán)的初始化、更新和結(jié)束條件。當循環(huán)表達式過于復雜或者含有的循環(huán)變量隨循環(huán)體本身中的條件變化而改變量值時,許多編譯器不生成零開銷的循環(huán)。基于這種準則,循環(huán)表達式應寫的盡可能清楚。并且盡量地對表達式做預處理,如將常數(shù)表達式移出循環(huán),預先計算結(jié)果等。如下給出典型情況下的分析結(jié)果:
雖然在循環(huán)中字符串的長度沒有改變,函數(shù)strlen()卻被調(diào)用了strlen(s)+1次。編譯器不能優(yōu)化這種多余調(diào)用函數(shù)的情況。一般情況下,一次函數(shù)調(diào)用返回一個不同的值,循環(huán)體對函數(shù)的結(jié)果產(chǎn)生影響。一種較好的編程思路是不對中間變量進行存儲,循環(huán)改為for(i=strlen(s)-1;i>=0;i--)。
以上從多方面探討了用C語言開發(fā)DSP軟件時的一些優(yōu)化考慮。為了有效的用C編程,應該按“程序是怎樣在匯編語言中執(zhí)行”的思想來編程。隨著實時操作系統(tǒng)、嵌入式操作系統(tǒng)、可視開發(fā)環(huán)境的引入,以及以DSP為平臺的C編譯器的功能不斷完善,用C開發(fā)DSP應用將更便捷。
參考文獻
[1]Robert Jan Ridder programming digital signal processors with high-level languages DSP Engineering, 2000
[2]Numerix -′Techniques For Optimizing C Code′ http://wwwnumerix-dspcom/om/
[3]Joseph Lemieux JMoving Efficiently from Assembly Language to C Class #401 Embedded Systems Conference Papers,2000
[4]TMS320C54x Optimizing C/C++Compiler User′s Guide.Texas Instruments Inc.,2001(6)
[5][JP4]TMS320C6000 Optimizing Compiler User′s Guide.Texas Instruments Incorporated,2001(4)
[6]劉朝暉,鄭玉墻.用C語言進行DSP軟件設計的優(yōu)化考慮.空軍雷達學院學報,2001(6)