本文來自:智趣網-C/C++語言編程技術交流論壇
http://www.bczh.net 在性能優化方面永遠注意80-20原則,即20%的程序消耗了80%的運行時間,於是咱們要改進效率,最主要是考慮改進那20%的代碼。不要優化程序中開銷不大的那80%,這是勞而無功的。 第一招:以空間換時間 計算機程序中最大的矛盾是空間和時間的矛盾,那麼,從這個角度出發逆向思惟來考慮程序的效率問題,咱們就有了解決問題的第1招--以空間換時間。好比說字符串的賦值: 方法A:一般的辦法 #define LEN 32 char string1 [LEN]; memset (string1,0,LEN); strcpy (string1,"This is a example!!"); 方法B: const char string2[LEN] ="This is a example!"; char * cp; cp = string2 ; 使用的時候能夠直接用指針來操做。 從上面的例子能夠看出,A和B的效率是不能比的。在一樣的存儲空間下,B直接使用指針就能夠操做了,而A須要調用兩個字符函數才能完成。B的缺點在於靈活性沒有A好。在須要頻繁更改一個字符串內容的時候,A具備更好的靈活性;若是採用方法B,則須要預存許多字符串,雖然佔用了大量的內存,可是得到了程序執行的高效率。 若是系統的實時性要求很高,內存還有一些,那我推薦你使用該招數。 第二招: 使用宏而不是函數。 這也是第一招的變招。函數和宏的區別就在於,宏佔用了大量的空間,而函數佔用了時間。你們要知道的是,函數調用是要使用系統的棧來保存數據的,若是編譯器裏有棧檢查選 項,通常在函數的頭會嵌入一些彙編語句對當前棧進行檢查;同時,CPU也要在函數調用時保存和恢復當前的現場,進行壓棧和彈棧操做,因此,函數調用須要一 些CPU時間。 而宏不存在這個問題。宏僅僅做爲預先寫好的代碼嵌入到當前程序,不會產生函數調用,因此僅僅是佔用了空間,在頻繁調用同一個宏的時候,該現象尤爲突出。 舉例以下: 方法C: #define bwMCDR2_ADDRESS 4 #define bsMCDR2_ADDRESS 17 int BIT_MASK(int __bf) { return ((1U << (bw ## __bf)) - 1)<< (bs ## __bf); } void SET_BITS(int __dst, int __bf, int __val) { __dst = ((__dst) & ~(BIT_MASK(__bf))) | \ (((__val) << (bs ## __bf)) & (BIT_MASK(__bf)))) } SET_BITS(MCDR2, MCDR2_ADDRESS,ReGISterNumber); 方法D: #define bwMCDR2_ADDRESS 4 #define bsMCDR2_ADDRESS 17 #define bmMCDR2_ADDRESS BIT_MASK(MCDR2_ADDRESS) #define BIT_MASK(__bf) (((1U << (bw ## __bf)) - 1) << (bs ## __bf)) #define SET_BITS(__dst, __bf, __val) \ ((__dst) = ((__dst) & ~(BIT_MASK(__bf))) | \ (((__val) << (bs ## __bf)) & (BIT_MASK(__bf)))) SET_BITS(MCDR2, MCDR2_ADDRESS, RegisterNumber); D方法是我看到的最好的置位操做函數,是arm公司源碼的一部分,在短短的三行內實現了不少功能,幾乎涵蓋了全部的位操做功能。C方法是其變體,其中滋味還需你們仔細體會。第三招:數學方法解決問題 如今咱們演繹高效C語言編寫的第二招--採用數學方法來解決問題。數學是計算機之母,沒有數學的依據和基礎,就沒有計算機的發展,因此在編寫程序的時候,採用一些數學方法會對程序的執行效率有數量級的提升。舉例以下,求 1~100的和。 方法E: int I , j; for (I = 1 ;I<=100; I ++) { j += I; } 方法F int I; I = (100 * (1+100)) / 2 這個例子是我印象最深的一個數學用例,是個人計算機啓蒙老師考個人。當時我只有小學三年級,惋惜我當時不知道用公式 N×(N+1)/ 2 來解決這個問題。方法E循環了100次才解決問題,也就是說最少用了100個賦值,100個判斷,200個加法(I和j);而方法F僅僅用了1個加法,1 次乘法,1次除法。效果天然不言而喻。因此,如今我在編程序的時候,更多的是動腦筋找規律,最大限度地發揮數學的威力來提升程序運行的效率。 第四招:使用位操做 使用位操做。減小除法和取模的運算。在計算機程序中數據的位是能夠操做的最小數據單位,理論上能夠用"位運算"來完成全部的運算和操做。通常的位操做是用來控制硬件的,或者作數據變換使用,可是,靈活的位操做能夠有效地提升程序運行的效率。舉例以下: 方法G int I,J; I = 257 /8; J = 456 % 32; 方法H int I,J; I = 257 >>3; J = 456 - (456 >> 4 << 4); 在字面上好像H比G麻煩了好多,可是,仔細查看產生的彙編代碼就會明白,方法G調用了基本的取模函數和除法函數,既有函數調用,還有不少彙編代碼和寄存器參與運算;而方法H則僅僅是幾句相關的彙編,代碼更簡潔,效率更高。固然,因爲編譯器的不一樣,可能效率的差距不大,可是,以我目前遇到的MS C ,arm C 來看,效率的差距仍是不小。 對於以2的指數次方爲"*"、"/"或"%"因子的數學運算,轉化爲移位運算"<< >>"一般能夠提升算法效率。由於乘除運算指令週期一般比移位運算大。 C語言位運算除了能夠提升運算效率外,在嵌入式系統的編程中,它的另外一個最典型的應用,並且十分普遍地正在被使用着的是位間的與(&)、或(|)、非(~)操做,這跟嵌入式系統的編程特色有很大關係。咱們一般要對硬件寄存器進行位設置,譬如,咱們經過將AM186ER型80186處理器的中斷屏蔽控制寄存器的第低6位設置爲0(開中斷2),最通用的作法是: #define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); outword(INT_MASK, wTemp &~INT_I2_MASK); 而將該位設置爲1的作法是: #define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); outword(INT_MASK, wTemp | INT_I2_MASK); 判斷該位是否爲1的作法是: #define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); if(wTemp & INT_I2_MASK) { … /* 該位爲1 */ } 運用這招須要注意的是,由於CPU的不一樣而產生的問題。好比說,在PC上用這招編寫的程序,並在PC上調試經過,在移植到一個16位機平臺上的時候,可能會產生代碼隱患。因此只有在必定技術進階的基礎下才可使用這招。 第五招:彙編嵌入 在熟悉彙編語言的人眼裏,C語言編寫的程序都是垃圾"。這種說法雖然偏激了一些,可是卻有它的道理。彙編語言是效率最高的計算機語言,可是,不可能靠着它來寫一個操做系統吧?因此,爲了得到程序的高效率,咱們只好採用變通的方法--嵌入彙編,混合編程。嵌入式C程序中主要使用在線彙編,即在C程序中直接插入_asm{ }內嵌彙編語句。 舉例以下,將數組一賦值給數組二,要求每一字節都相符。 char string1[1024],string2[1024]; 方法I int I; for (I =0 ;I<1024;I++) *(string2 + I) = *(string1 + I) 方法J #ifdef _PC_ int I; for (I =0 ;I<1024;I++) *(string2 + I) = *(string1 + I); #else #ifdef _arm_ __asm { MOV R0,string1 MOV R1,string2 MOV R2,#0 loop: LDMIA R0!, [R3-R11] STMIA R1!, [R3-R11] ADD R2,R2,#8 CMP R2, #400 BNE loop } #endif 再舉個例子: /* 把兩個輸入參數的值相加,結果存放到另一個全局變量中 */ int result; void Add(long a, long *b) { _asm { MOV AX, a MOV BX, b ADD AX, [BX] MOV result, AX } } 方法I是最多見的方法,使用了1024次循環;方法J則根據平臺不一樣作了區分,在arm平臺下,用嵌入彙編僅用128次循環就完成了一樣的操做。這裏有朋友會說,爲何不用標準的內存拷貝函數呢?這是由於在源數據裏可能含有數據爲0的字節,這樣的話,標準庫函數會提早結束而不會完成咱們要求的操做。這個例程典型應用於LCD數據的拷貝過程。根據不一樣的CPU,熟練使用相應的嵌入彙編,能夠大大提升程序執行的效率。 雖然是必殺技,可是若是輕易使用會付出慘重的代價。這是由於,使用了嵌入彙編,便限制了程序的可移植性,使程序在不一樣平臺移植的過程當中,臥虎藏龍,險象環生!同時該招數也與現代軟件工程的思想相違背,只有在無可奈何的狀況下才能夠採用。 第六招, 使用寄存器變量 當對一個變量頻繁被讀寫時,須要反覆訪問內存,從而花費大量的存取時間。爲此,C語言提供了一種變量,即寄存器變量。這種變量存放在CPU的寄存器中,使用時,不須要訪問內存,而直接從寄存器中讀寫,從而提升效率。寄存器變量的說明符是register。對於循環次數較多的循環控制變量及循環體內反覆使用的變量都可定義爲寄存器變量,而循環計數是應用寄存器變量的最好候選者。 (1) 只有局部自動變量和形參才能夠定義爲寄存器變量。由於寄存器變量屬於動態存儲方式,凡須要採用靜態存儲方式的量都不能定義爲寄存器變量,包括:模塊間全局變量、模塊內全局變量、局部static變量; (2) register是一個"建議"型關鍵字,意指程序建議該變量放在寄存器中,但最終該變量可能由於條件不知足並未成爲寄存器變量,而是被放在了存儲器中,但編譯器中並不報錯(在C++語言中有另外一個"建議"型關鍵字:inline)。 下面是一個採用寄存器變量的例子: /* 求1+2+3+….+n的值 */ WORD Addition(BYTE n) { register i,s=0; for(i=1;i<=n;i++) { s=s+i; } return s; } 本程序循環n次,i和s都被頻繁使用,所以可定義爲寄存器變量。 /*www.cyuyan.com.cn*/ 第七招: 利用硬件特性 首先要明白CPU對各類存儲器的訪問速度,基本上是: CPU內部RAM > 外部同步RAM > 外部異步RAM > FLASH/ROM 對於程序代碼,已經被燒錄在FLASH或ROM中,咱們可讓CPU直接從其中讀取代碼執行,但一般這不是一個好辦法,咱們最好在系統啓動後將FLASH或ROM中的目標代碼拷貝入RAM中後再執行以提升取指令速度; 對於UART等設備,其內部有必定容量的接收BUFFER,咱們應儘可能在BUFFER被佔滿後再向CPU提出中斷。例如計算機終端在向目標機經過RS-232傳遞數據時,不宜設置UART只接收到一個BYTE就向CPU提中斷,從而無謂浪費中斷處理時間; 若是對某設備能採起DMA方式讀取,就採用DMA讀取,DMA讀取方式在讀取目標中包含的存儲信息較大時效率較高,其數據傳輸的基本單位是塊,而所傳輸的數據是從設備直接送入內存的(或者相反)。DMA方式較之中斷驅動方式,減小了CPU 對外設的干預,進一步提升了CPU與外設的並行操做程度。