欧美在线专区-欧美在线伊人-欧美在线一区二区三区欧美-欧美在线一区二区三区-pornodoxxx中国妞-pornodoldoo欧美另类

position>home>Blogs

C語言執(zhí)行效率如何保證,看這一文就夠了!

[導讀]如何保證C語言的語言執(zhí)執(zhí)行效率?

來自公眾號:嵌入式ARM
嵌入式開發(fā)基本都會選擇C語言
這是因為C語言有出色的可移植性
能在多種不同體系結構的軟/硬平臺上運行
雖然代碼的復用性差
代碼的維護性差
擴展性很差

但,C語言簡潔緊湊
使用靈活的行效語法機制
并且,C語言具有很高的率何運行效率

那么如何保證C語言的執(zhí)行效率?
嵌入式ARM告訴你!

01

C語言執(zhí)行效率如何保證,看這一文就夠了!

C代碼執(zhí)行效率與哪些因素有關


C代碼執(zhí)行效率與時間復雜度空間復雜度有關:

1、保證空間復雜度是看文指算法在計算機內執(zhí)行時所需存儲空間的度量

2、一般情況下,語言執(zhí)算法中基本操作重復執(zhí)行的行效次數(shù)是問題規(guī)模n的某個函數(shù),用T(n)表示,率何若有某個輔助函數(shù)f(n),使得當n趨近于無窮大時,保證T(n)/f(n)的看文極限值為不等于零的常數(shù),則稱f(n)是語言執(zhí)T(n)的同數(shù)量級函數(shù)。

記作T(n)=O(f(n)),稱O(f(n))為算法的行效漸進時間復雜度,簡稱時間復雜度。率何在各種不同算法中,保證若算法中語句執(zhí)行次數(shù)為一個常數(shù),看文則時間復雜度為O(1),另外,在時間頻度不相同時,時間復雜度有可能相同,如T(n)=n2+3n+4與T(n)=4n2+2n+1它們的頻度不同,但時間復雜度相同,都為O(n2)。

按數(shù)量級遞增排列,常見的時間復雜度有:常數(shù)階O(1),對數(shù)階O(log2n),線性階O(n),線性對數(shù)階O(nlog2n),平方階O(n^2),立方階O(n^3),。。。,k次方階O(n^k),指數(shù)階O(2^n)。隨著問題規(guī)模n的不斷增大,上述時間復雜度不斷增大,算法的執(zhí)行效率越低。

02

保障C代碼執(zhí)行效率的原則


1、選擇合適的算法和數(shù)據(jù)結構
選擇一種合適的數(shù)據(jù)結構很重要,如果在一堆隨機存放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。數(shù)組與指針語句具有十分密切的關系,一般來說,指針比較靈活簡潔,而數(shù)組則比較直觀,容易理解。對于大部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。

在許多種情況下,可以用指針運算代替數(shù)組索引,這樣做常常能產(chǎn)生又快又短的代碼。與數(shù)組索引相比,指針一般能使代碼速度更快,占用空間更少。使用多維數(shù)組時差異更明顯。下面的代碼作用是相同的,但是效率不一樣。

    數(shù)組索引 指針運算For(;;){  p=arrayA=array[t++]; for(;;){ a=*(p++);。。。。。。。。。。。。。。。????}??????????????????????}

    指針方法的優(yōu)點是,array的地址每次裝入地址p后,在每次循環(huán)中只需對p增量操作。在數(shù)組索引方法中,每次循環(huán)中都必須根據(jù)t值求數(shù)組下標的復雜運算。

    時間復雜度更低、效率更高的算法可以提高執(zhí)行效率。一個簡單的例子,計算1~100這些數(shù)的和,可以循環(huán)100次,也可以直接使用求和公式,在執(zhí)行效率上,是顯而易見的。
    2、代碼盡量簡潔,避免重復


    在10天學會單片機那本書上看到寫的數(shù)碼管顯示那部分代碼,選中一個位,然后送數(shù)據(jù),再選中一個位,再送數(shù)據(jù),依次做完。代碼重復率太高了,不僅占用過多的類存,而且執(zhí)行效率差可讀性差,僅僅是實現(xiàn)了功能而已,實際的編程可以做一個循環(huán),for循環(huán)或者while循環(huán)。這樣的代碼看起來更有水平。

    3、合理使用宏定義

    在程序中如果某個變量或寄存器經(jīng)常用到,可以使用宏定義定義一個新的名代替它。這樣的好處是方便修改,比如液晶的數(shù)據(jù)端總線接的P1,現(xiàn)在想改到P0,那么只需要修改宏定義這里就可以了,編譯器編譯的時候,會自動的把定義的名替換成實際的名稱。


    函數(shù)和宏的區(qū)別就在于,宏占用了大量的空間,而函數(shù)占用了時間。大家要知道的是,函數(shù)調用是要使用系統(tǒng)的棧來保存數(shù)據(jù)的,如果編譯器 里有棧檢查選 項,一般在函數(shù)的頭會嵌入一些匯編語句對當前棧進行檢查;同時,CPU也要在函數(shù)調用時保存和恢復當前的現(xiàn)場,進行壓棧和彈棧操作,所以,函數(shù)調用需要一 些CPU時間。而宏不存在這個問題。宏僅僅作為預先寫好的代碼嵌入到當前程序,不會產(chǎn)生函數(shù)調用,所以僅僅是占用了空間,在頻繁調用同一個宏的時候,該現(xiàn)象尤其突出。

    舉例如下:

    方法A:


      #define bwMCDR2_ADDRESS 4#define bsMCDR2_ADDRESS 17int 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);


      方法B:


        #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);


        B方法是我看到的最好的置位操作函數(shù),是ARM公司源碼的一部分,在短短的三行內實現(xiàn)了很多功能,幾乎涵蓋了所有的位操作功能。A方法是其變體,其中滋味還需大家仔細體會。

        4、以空間換取時間


        程序的復雜度包含時間復雜度和空間復雜度,而隨著計算機硬件的發(fā)展,漸漸放低了對空間復雜度的要求,在很多情況下,為了換取程序的執(zhí)行效率,犧牲計算機的空間。比如字符串的相關操作、使用緩存技術等。


        計算機程序中最大的矛盾是空間和時間的矛盾,那么,從這個角度出發(fā)逆向思維來考慮程序的效率問題,我們就有了解決問題的第1招--以空間換時間。比如說字符串的賦值:

        方法A:通常的辦法


          #define LEN 32char 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需要調用兩個字符函數(shù)才能完成。B的缺點在于靈活性沒有A好。在需要頻繁更改一個字符串內容的時候,A具有更好的靈活性;如果采用方法B,則需要預存許多字符串,雖然占用了大量的內存,但是獲得了程序執(zhí)行 的高效率。

            如果系統(tǒng)的實時性要求很高,內存還有一些,那我推薦你使用該招數(shù)。

            5、選擇合適的算法和數(shù)據(jù)結構

            應該熟悉算法語言,知道各種算法的優(yōu)缺點,具體資料請參見相應的參考資料,有很多計算機書籍上都有介紹。將比較慢的順序查找法用較快的二分查找或亂序查找法代替,插入排序或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大提高程序執(zhí)行的效率。

            選擇一種合適的數(shù)據(jù)結構也很重要。指針是一個包含地址的變量,可對他指向的變量進行尋址。使用指針可以很容易的從一個變量移到下一個變量,故特別適合對大量變量進行操作的場合。數(shù)組與指針語句具有十分密切的關系,一般來說,指針比較靈活簡潔,而數(shù)組則比較直觀,容易理解。對于大部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。但是在Keil中則相反,使用數(shù)組比使用的指針生成的代碼更短。

            6、使用條件編譯

            一般情況下對C語言程序進行編譯時,所有的程序都參加編譯,但是有時希望對其中一部分內容只在滿足一定條件才編譯,這就是條件編譯。條件編譯可以根據(jù)實際情況,選擇不同的編譯范圍,從而產(chǎn)生不同的代碼。

            7、嵌入?yún)R編——殺手锏譯

            匯編語言是效率最高的計算機語言,在一般項目開發(fā)當中一般都采用C語言來開發(fā)的,因為嵌入?yún)R編之后會影響平臺的移植性和可讀性,不同平臺的匯編指令是不兼容的。但是對于一些執(zhí)著的程序員要求程序獲得極致的運行的效率,他們都在C語言中嵌入?yún)R編,即“混合編程”。

              /*實現(xiàn)了a=a+b+c*/__asm {  push eax mov eax,a add eax,b add eax,c mov a,eax pop eax }

              手動編寫匯編。在嵌入式軟件開發(fā)中,一些軟件模塊最好用匯編語言來寫,這可以使程序更加有效。雖然C/C++編譯器對代碼進行了優(yōu)化,但是適當?shù)氖褂脙嚷?lián)匯編指令可以有效的提高整個系統(tǒng)運行的效率。

              在熟悉匯編語言的人眼里,C語言編寫的程序都是垃圾"。這種說法雖然偏激了一些,但是卻有它的道理。匯編語言是效率最高的計算機語言,但是,不可能靠著它 來寫一個操作系統(tǒng)吧?所以,為了獲得程序的高效率,我們只好采用變通的方法--嵌入?yún)R編,混合編程。嵌入式C程序中主要使用在線匯編,即在C程序中直接插 入_asm{ }內嵌匯編語句。
              舉例如下,將數(shù)組一賦值給數(shù)組二,要求每一字節(jié)都相符。
              char string1[1024],string2[1024];

              方法A:

                int I;for (I =0 ;I<1024;I++)*(string2 + I) = *(string1 + I)

                方法B:

                  #ifdef _PC_int I;for (I =0 ;I<1024;I++)*(string2 + I) = *(string1 + I);#else#ifdef _ARM___asm{ MOV R0,string1MOV R1,string2MOV R2,#0loop:LDMIA R0!, [R3-R11]STMIA R1!, [R3-R11]ADD R2,R2,#8CMP R2, #400BNE loop}#endif

                  再舉個例子:

                    /* 把兩個輸入?yún)?shù)的值相加,結果存放到另外一個全局變量中 */int result;void Add(long a, long *b){ _asm{ MOV AX, aMOV BX, bADD AX, [BX]MOV result, AX}}

                    方法A是最常見的方法,使用了1024次循環(huán);方法J則根據(jù)平臺不同做了區(qū)分,在ARM平臺下,用嵌入?yún)R編僅用128次循環(huán)就完成了同樣的操作。這里有朋友 會說,為什么不用標準的內存拷貝函數(shù)呢?這是因為在源數(shù)據(jù)里可能含有數(shù)據(jù)為0的字節(jié),這樣的話,標準庫函數(shù)會提前結束而不會完成我們要求的操作。這個例程 典型應用于LCD數(shù)據(jù)的拷貝過程。根據(jù)不同的CPU,熟練使用相應的嵌入?yún)R編,可以大大提高程序執(zhí)行的效率。

                    雖然是必殺技,但是如果輕易使用會付出慘重的代價。這是因為,使用了嵌入?yún)R編,便限制了程序的可移植性,使程序在不同平臺移植的過程中,臥虎藏龍,險象環(huán)生!同時該招數(shù)也與現(xiàn)代軟件工程的思想相違背,只有在迫不得已的情況下才可以采用。

                    8、避免使用標準庫例程

                    嵌入式系統(tǒng)編程應避免使用標準庫例程,因為很多大的庫例程設法處理所有可能的情況,所以占用了龐大的內存空間,因而應盡可能地減少使用標準庫例程。

                    9、確保聲明和定義是靜態(tài)的,除非您希望從不同的文件中調用該函數(shù)。

                    在同一文件函數(shù)對其他函數(shù)可見,才稱之為靜態(tài)函數(shù)。它限制其他訪問內部函數(shù),如果我們希望從外界隱藏該函數(shù)。現(xiàn)在我們并不需要為內部函數(shù)創(chuàng)建頭文件,其他看不到該函數(shù)。靜態(tài)聲明一個函數(shù)的優(yōu)點包括:

                    (1)兩個或兩個以上具有相同名稱的靜態(tài)函數(shù),可用于在不同的文件。
                    (2)編譯消耗減少,因為沒有外部符號處理。

                    10、數(shù)學方法解決問題

                    現(xiàn)在我們演繹高效C語言編寫的第二招--采用數(shù)學方法來解決問題。數(shù)學是計算機之母,沒有數(shù)學的依據(jù)和基礎,就沒有計算機的發(fā)展,所以在編寫程序的時候,采用一些數(shù)學方法會對程序的執(zhí)行效率有數(shù)量級的提高。舉例如下,求 1~100的和。

                    方法A:


                      int I , j;for (I = 1 ;I<=100; I ++){ j += I;}


                      方法B:


                        int I;I = (100 * (1+100)) / 2


                        這個例子是我印象最深的一個數(shù)學用例,是我的計算機啟蒙老師考我的。當時我只有小學三年級,可惜我當時不知道用公式 N×(N+1)/ 2 來解決這個問題。方法E循環(huán)了100次才解決問題,也就是說最少用了100個賦值,100個判斷,200個加法(I和j);而方法F僅僅用了1個加法,1次乘法,1次除法。效果自然不言而喻。所以,現(xiàn)在我在編程序的時候,更多的是動腦筋找規(guī)律,最大限度地發(fā)揮數(shù)學的威力來提高程序運行的效率。

                        11、使用位操作

                        使用位操作。減少除法和取模的運算。在計算機程序中數(shù)據(jù)的位是可以操作的最小數(shù)據(jù)單位,理論上可以用"位運算"來完成所有的運算和操作。一般的位操作是用來控制硬件的,或者做數(shù)據(jù)變換使用,但是,靈活的位操作可以有效地提高程序運行的效率。舉例如下:

                        方法A:


                          int?I,J;I = 257 /8;J = 456 % 32;


                          方法B:


                            int I,J;I = 257 >>3;J = 456 - (456 >>4 << 4);


                            在字面上好像B比A麻煩了好多,但是,仔細查看產(chǎn)生的匯編代碼就會明白,方法B調用了基本的取模函數(shù)和除法函數(shù),既有函數(shù)調用,還有很多匯編代碼和寄存器參 與運算;而方法H則僅僅是幾句相關的匯編,代碼更簡潔,效率更高。當然,由于編譯器的不同,可能效率的差距不大,但是,以我目前遇到的MS C ,ARM C 來看,效率的差距還是不小。

                            對于以2的指數(shù)次方為"*"、"/"或"%"因子的數(shù)學運算,轉化為移位運算"<< >>"通常可以提高算法效率。因為乘除運算指令周期通常比移位運算大。

                            C語言位運算除了可以提高運算效率外,在嵌入式系統(tǒng)的編程中,它的另一個最典型的應用,而且十分廣泛地正在被使用著的是位間的與(&)、或 (|)、非(~)操作,這跟嵌入式系統(tǒng)的編程特點有很大關系。我們通常要對硬件寄存器進行位設置,譬如,我們通過將AM186ER型80186處理器的中 斷屏蔽控制寄存器的第低6位設置為0(開中斷2),最通用的做法是:

                              #define INT_I2_MASK 0x0040wTemp = inword(INT_MASK);outword(INT_MASK, wTemp &~INT_I2_MASK);


                              而將該位設置為1的做法是:

                                #define INT_I2_MASK 0x0040wTemp = inword(INT_MASK);outword(INT_MASK,?wTemp?|?INT_I2_MASK);???


                                判斷該位是否為1的做法是:

                                  #define INT_I2_MASK 0x0040wTemp = inword(INT_MASK);if(wTemp & INT_I2_MASK){ … /* 該位為1 */}


                                  運用這招需要注意的是,因為CPU的不同而產(chǎn)生的問題。比如說,在PC上用這招編寫的程序,并在PC上調試通過,在移植到一個16位機平臺上的時候,可能會產(chǎn)生代碼隱患。所以只有在一定技術進階的基礎下才可以使用這招。

                                  12、利用硬件特性

                                  首先要明白CPU對各種存儲器的訪問速度,基本上是:

                                  CPU內部RAM > 外部同步RAM > 外部異步RAM > FLASH/ROM

                                  對于程序代碼,已經(jīng)被燒錄在FLASH或ROM中,我們可以讓CPU直接從其中讀取代碼執(zhí)行,但通常這不是一個好辦法,我們最好在系統(tǒng)啟動后將FLASH或ROM中的目標代碼拷貝入RAM中后再執(zhí)行以提高取指令速度;

                                  對于UART等設備,其內部有一定容量的接收BUFFER,我們應盡量在BUFFER被占滿后再向CPU提出中斷。例如計算機終端在向目標機通過RS-232傳遞數(shù)據(jù)時,不宜設置UART只接收到一個BYTE就向CPU提中斷,從而無謂浪費中斷處理時間;

                                  如果對某設備能采取DMA方式讀取,就采用DMA讀取,DMA讀取方式在讀取目標中包含的存儲信息較大時效率較高,其數(shù)據(jù)傳輸?shù)幕締挝皇菈K,而所傳輸 的數(shù)據(jù)是從設備直接送入內存的(或者相反)。DMA方式較之中斷驅動方式,減少了CPU 對外設的干預,進一步提高了CPU與外設的并行操作程度。

                                  13、使用寄存器變量


                                  當對一個變量頻繁被讀寫時,需要反復訪問內存,從而花費大量的存取時間。為此,C語言提供了一種變量,即寄存器變量。這種變量存放在CPU的寄存器中,使 用時,不需要訪問內存,而直接從寄存器中讀寫,從而提高效率。寄存器變量的說明符是register。對于循環(huán)次數(shù)較多的循環(huán)控制變量及循環(huán)體內反復使用 的變量均可定義為寄存器變量,而循環(huán)計數(shù)是應用寄存器變量的最好候選者。

                                  (1) 只有局部自動變量和形參才可以定義為寄存器變量。因為寄存器變量屬于動態(tài)存儲方式,凡需要采用靜態(tài)存儲方式的量都不能定義為寄存器變量,包括:模塊間全局變量、模塊內全局變量、局部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;}


                                    本程序循環(huán)n次,i和s都被頻繁使用,因此可定義為寄存器變量。


                                    03

                                    保障C代碼執(zhí)行效率的優(yōu)化方法


                                    1、使用盡量小的數(shù)據(jù)類型

                                    比如某個變量的值范圍是0~255,那么就定義成unsignedchar,當然也可以定義成unsignedint,但是這樣造成了內存的浪費,而且運算時效率要低一點。

                                    如果數(shù)據(jù)沒有負數(shù)的話,盡量定義成無符號的類型。應盡量避免定義成浮點型數(shù)據(jù)類型或雙精度(占8個字節(jié))類型,這兩種類型運算時很消耗CPU資源。

                                    比如采集電壓范圍是0-5v,精確到小數(shù)點后三位,可以把采集到的數(shù)據(jù)擴大1000倍,即使最大也才到5000,然后多采集幾次做個濾波算法,最后電壓算出來后只需要在第一位后面加個小數(shù)點就可以了,變量定義成unsignedint型變量就沒問題了。

                                    2、盡量避免調用延時函數(shù)

                                    沒有帶操作系統(tǒng)的程序只能在while(1)里面循環(huán)執(zhí)行,如果在這里面調用大量的延時這樣會很消耗CPU的資源,延時等于是讓它在這歇著不干事了,只有中斷里面的才會執(zhí)行。如果僅僅是做一個LED一秒閃爍一次的程序,那么很簡單,可以直接調用延時函數(shù),但是實際的項目中往往在大循環(huán)里有很多事要做,對于實時性要求較高的場合就不行了。

                                    為了避免使用延時,可以使用定時器中斷產(chǎn)生一個標志位,到了時間標志位置1,在主程序里面只需要檢測標志位,置1了才執(zhí)行一次,然后清標志。其他時間就去做別的事了,而不會在這等待了。

                                    最好的例子就是數(shù)碼管的顯示,使用中斷調顯示。然后是按鍵檢測,一般的程序都是做的while(!key)等待按鍵釋放,如果按鍵一直按著,那后面的程序就永遠得不到運行死在這了,其實可以做一個按鍵標志檢測下降沿和上升沿就可以避免這個問題了。


                                    3、避免使用乘除法

                                    乘除法很消耗CPU資源,查看匯編代碼會發(fā)現(xiàn),一個乘除法運算會編譯出10幾甚至幾10行代碼。如果是乘以或除以2的n次方,可以用<<或>>來實現(xiàn),這種移位運算在編譯時就已經(jīng)算好了,所以代碼很簡潔,運算效率就高。但是需要特別注意運算符的優(yōu)先級問題。

                                    4、盡量使用復合賦值運算符

                                    a=a+b與a+=b這兩個表達式有什么區(qū)別呢?

                                    前者是先計算a+b的值,然后保存到ACC寄存器,再把ACC寄存器的值賦給a。而后者是直接將a+b的值賦給a,節(jié)省一個步驟。雖然只節(jié)省了一條指令,但是當這個運算循環(huán)幾千次幾萬次呢?那么效果很明顯了。

                                    像其他的-=、*=、/=、%=等都是一樣的。

                                    5、不定義不使用的返回值。

                                    function函數(shù)定義并不知道函數(shù)返回值是否被使用,假如返回值從來不會被用到,應該使用void來明確聲明函數(shù)不返回任何值。

                                    6、在一個邏輯條件語句中常數(shù)項永遠在左側。
                                      int x = 4; if (x = 1){ x = x + 2;printf("%d",x);// Output is 3 }int x = 4;if (1 = x){ x = x + 2;printf("%d",x);// Compilation error}

                                      7、減少運算的強度


                                      (1)、查表(游戲程序員必修課)


                                      一個聰明的游戲大蝦,基本上不會在自己的主循環(huán)里搞什么運算工作,絕對是先計算好了,再到循環(huán)里查表。看下面的例子:

                                      舊代碼:

                                        long factorial(int i){ if (i == 0)return 1;elsereturn i * factorial(i - 1);}新代碼:static long factorial_table[] ={ 1, 1, 2, 6, 24, 120, 720 /* etc */ };long factorial(int i){ return factorial_table[i];}


                                        如果表很大,不好寫,就寫一個init函數(shù),在循環(huán)外臨時生成表格。


                                        (2)、求余運算


                                        a=a%8;

                                        可以改為:

                                        ?a=a&7;

                                        說明:位操作只需一個指令周期即可完成,而大部分的C編譯器的“%”運算均是調用子程序來完成,代碼長、執(zhí)行速度慢。通常,只要求是求2n方的余數(shù),均可使用位操作的方法來代替。


                                        (3)、平方運算


                                        a=pow(a, 2.0);

                                        可以改為:

                                        a=a*a;

                                        說明:在有內置硬件乘法器的單片機中(如51系列),乘法運算比求平方運算快得多,因為浮點數(shù)的求平方是通過調用子程序來實現(xiàn)的,在自帶硬件乘法器的AVR單片機中,如ATMega163中,乘法運算只需2個時鐘周期就可以完成。既使是在沒有內置硬件乘法器的AVR單片機中,乘法運算的子程序比平方運算的子程序代碼短,執(zhí)行速度快。

                                        如果是求3次方,如:

                                        a=pow(a,3。0);

                                        更改為:

                                        a=a*a*a;

                                        則效率的改善更明顯。


                                        (4)、用移位實現(xiàn)乘除法運算


                                        a=a*4;
                                        b=b/4;

                                        可以改為:

                                        a=a<<2;
                                        b=b>>2;

                                        通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果乘以2n,都可以生成左移的代碼,而乘以其它的整數(shù)或除以任何數(shù),均調用乘除法子程序。用移位的方法得到代碼比調用乘除法子程序生成的代碼效率高。實際上,只要是乘以或除以一個整數(shù),均可以用移位的方法得到結果,如:

                                        a=a*9

                                        可以改為:

                                        a=(a<<3)+a

                                        采用運算量更小的表達式替換原來的表達式,下面是一個經(jīng)典例子:

                                        舊代碼:

                                          ????x?=?w?%?8;y = pow(x, 2.0);z = y * 33;for (i = 0;i < MAX;i++){ h = 14 * i;printf("%d", h);}新代碼:x = w & 7; /* 位操作比求余運算快 */y = x * x; /* 乘法比平方運算快 */z = (y << 5) + y; /* 位移乘法比乘法快 */for (i = h = 0; i < MAX; i++){ h += 14; /* 加法比乘法快 */printf("%d", h);}

                                          (5)、避免不必要的整數(shù)除法


                                          整數(shù)除法是整數(shù)運算中最慢的,所以應該盡可能避免。一種可能減少整數(shù)除法的地方是連除,這里除法可以由乘法代替。這個替換的副作用是有可能在算乘積時會溢出,所以只能在一定范圍的除法中使用。

                                          不好的代碼:
                                          int i,?j,?k,?m;
                                          m = i / j / k;

                                          推薦的代碼:
                                          int i,?j,?k,?m;
                                          m = i / (j * k);


                                          (6)、使用增量和減量操作符


                                          在使用到加一和減一操作時盡量使用增量和減量操作符,因為增量符語句比賦值語句更快,原因在于對大多數(shù)CPU來說,對內存字的增、減量操作不必明顯地使用取內存和寫內存的指令,比如下面這條語句:
                                          x=x+1;

                                          模仿大多數(shù)微機匯編語言為例,產(chǎn)生的代碼類似于:

                                          move A,x???? ?;把x從內存取出存入累加器A
                                          add A,1?????? ?;累加器A加1
                                          store x???????? ?;把新值存回x

                                          如果使用增量操作符,生成的代碼如下:

                                          incr x????????? ?;x加1

                                          顯然,不用取指令和存指令,增、減量操作執(zhí)行的速度加快,同時長度也縮短了。


                                          (7)、使用復合賦值表達式


                                          復合賦值表達式(如a-=1及a+=1等)都能夠生成高質量的程序代碼。


                                          (8)、提取公共的子表達式


                                          在某些情況下,C++編譯器不能從浮點表達式中提出公共的子表達式,因為這意味著相當于對表達式重新排序。需要特別指出的是,編譯器在提取公共子表達式前不能按照代數(shù)的等價關系重新安排表達式。這時,程序員要手動地提出公共的子表達式(在VC.NET里有一項“全局優(yōu)化”選項可以完成此工作,但效果就不得而知了)。

                                          不好的代碼:

                                            float a, b, c, d, e, f;。。。e = b * c / d;f = b / d * a;


                                            推薦的代碼:

                                              float a, b, c, d, e, f;。。。const float t(b / d);e = c * t;f = a * t;

                                              不好的代碼:

                                                float a, b, c, e, f;。。。e = a / c;f = b / c;


                                                推薦的代碼:

                                                  float a, b, c, e, f;。。。const float t(1.0f / c);e = a * t;f = b * t;


                                                  8、結構體成員的布局


                                                  很多編譯器有“使結構體字,雙字或四字對齊”的選項。但是,還是需要改善結構體成員的對齊,有些編譯器可能分配給結構體成員空間的順序與他們聲明的不同。但是,有些編譯器并不提供這些功能,或者效果不好。所以,要在付出最少代價的情況下實現(xiàn)最好的結構體和結構體成員對齊,建議采取下列方法:


                                                  (1)按數(shù)據(jù)類型的長度排序


                                                  把結構體的成員按照它們的類型長度排序,聲明成員時把長的類型放在短的前面。編譯器要求把長型數(shù)據(jù)類型存放在偶數(shù)地址邊界。在申明一個復雜的數(shù)據(jù)類型?(既有多字節(jié)數(shù)據(jù)又有單字節(jié)數(shù)據(jù))?時,應該首先存放多字節(jié)數(shù)據(jù),然后再存放單字節(jié)數(shù)據(jù),這樣可以避免內存的空洞。編譯器自動地把結構的實例對齊在內存的偶數(shù)邊界。


                                                  (2)把結構體填充成最長類型長度的整倍數(shù)


                                                  把結構體填充成最長類型長度的整倍數(shù)。照這樣,如果結構體的第一個成員對齊了,所有整個結構體自然也就對齊了。下面的例子演示了如何對結構體成員進行重新排序:

                                                  不好的代碼,普通順序:

                                                    struct{    char a[5];   long k;  double x;} baz;

                                                    推薦的代碼,新的順序并手動填充了幾個字節(jié):

                                                      struct{    double x;   long k;   char a[5];char pad[7];} baz;



                                                      這個規(guī)則同樣適用于類的成員的布局。


                                                      (3)按數(shù)據(jù)類型的長度排序本地變量


                                                      當編譯器分配給本地變量空間時,它們的順序和它們在源代碼中聲明的順序一樣,和上一條規(guī)則一樣,應該把長的變量放在短的變量前面。如果第一個變量對齊了,其它變量就會連續(xù)的存放,而且不用填充字節(jié)自然就會對齊。有些編譯器在分配變量時不會自動改變變量順序,有些編譯器不能產(chǎn)生4字節(jié)對齊的棧,所以4字節(jié)可能不對齊。下面這個例子演示了本地變量聲明的重新排序:

                                                      不好的代碼,普通順序

                                                        short ga, gu, gi;long foo, bar;double x, y, z[3];char a, b;float baz;推薦的代碼,改進的順序double z[3];double x, y;long foo, bar;float baz;short ga, gu, gi;



                                                        (4)把頻繁使用的指針型參數(shù)拷貝到本地變量


                                                        避免在函數(shù)中頻繁使用指針型參數(shù)指向的值。因為編譯器不知道指針之間是否存在沖突,所以指針型參數(shù)往往不能被編譯器優(yōu)化。這樣數(shù)據(jù)不能被存放在寄存器中,而且明顯地占用了內存帶寬。注意,很多編譯器有“假設不沖突”優(yōu)化開關(在VC里必須手動添加編譯器命令行/Oa或/Ow),這允許編譯器假設兩個不同的指針總是有不同的內容,這樣就不用把指針型參數(shù)保存到本地變量。否則,請在函數(shù)一開始把指針指向的數(shù)據(jù)保存到本地變量。如果需要的話,在函數(shù)結束前拷貝回去。

                                                        不好的代碼:

                                                          // 假設 q != rvoid isqrt(unsigned long a, unsigned long* q, unsigned long* r){   *q = a;  if (a >0)  {     while (*q >(*r = a / *q))    {       *q = (*q + *r) >>1;    }  }  *r = a - *q * *q;}


                                                          ?
                                                          推薦的代碼:

                                                            // 假設 q != rvoid isqrt(unsigned long a, unsigned long* q, unsigned long* r){   unsigned long qq, rr;  qq = a;  if (a >0)  {     while (qq >(rr = a / qq))    {       qq = (qq + rr) >>1;    }  }  rr = a - qq * qq;  *q = qq;  *r = rr;}



                                                            9、循環(huán)優(yōu)化


                                                            (1)、充分分解小的循環(huán)


                                                            要充分利用CPU的指令緩存,就要充分分解小的循環(huán)。特別是當循環(huán)體本身很小的時候,分解循環(huán)可以提高性能。注意:很多編譯器并不能自動分解循環(huán)。不好的代碼:

                                                            // 3D轉化:把矢量 V 和 4x4 矩陣 M 相乘for (i = 0;i < 4;i ++){   r[i] = 0;  for (j = 0;j < 4;j ++)  {     r[i] += M[j][i]*V[j];  }}推薦的代碼:

                                                            r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3];r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3];r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3];r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*v[3];




                                                            (2)、提取公共部分


                                                            對于一些不需要循環(huán)變量參加運算的任務可以把它們放到循環(huán)外面,這里的任務包括表達式、函數(shù)的調用、指針運算、數(shù)組訪問等,應該將沒有必要執(zhí)行多次的操作全部集合在一起,放到一個init的初始化程序中進行。


                                                            (3)、延時函數(shù)


                                                            通常使用的延時函數(shù)均采用自加的形式:

                                                              void delay (void){ unsigned int i;for (i=0;i<1000;i++) ;}

                                                              將其改為自減延時函數(shù):

                                                                void delay (void){ unsigned int i;for (i=1000;i>0;i--) ;}

                                                                兩個函數(shù)的延時效果相似,但幾乎所有的C編譯對后一種函數(shù)生成的代碼均比前一種代碼少1~3個字節(jié),因為幾乎所有的MCU均有為0轉移的指令,采用后一種方式能夠生成這類指令。在使用while循環(huán)時也一樣,使用自減指令控制循環(huán)會比使用自加指令控制循環(huán)生成的代碼更少1~3個字母。但是在循環(huán)中有通過循環(huán)變量“i”讀寫數(shù)組的指令時,使用預減循環(huán)有可能使數(shù)組超界,要引起注意。


                                                                (4)、while循環(huán)和do…while循環(huán)


                                                                用while循環(huán)時有以下兩種循環(huán)形式:

                                                                  unsigned int i;i=0;while (i<1000){ i++;//用戶程序}


                                                                  或:

                                                                    unsigned int i;i=1000;do{ i--;//用戶程序}while (i>0);

                                                                    在這兩種循環(huán)中,使用do…while循環(huán)編譯后生成的代碼的長度短于while循環(huán)。


                                                                    (6)、循環(huán)展開


                                                                    這是經(jīng)典的速度優(yōu)化,但許多編譯程序(如gcc -funroll-loops)能自動完成這個事,所以現(xiàn)在你自己來優(yōu)化這個顯得效果不明顯。

                                                                    舊代碼:

                                                                      for (i = 0; i < 100; i++){ do_stuff(i);}


                                                                      新代碼:

                                                                        for (i = 0; i < 100; ){ do_stuff(i); i++;do_stuff(i); i++;do_stuff(i); i++;do_stuff(i); i++;do_stuff(i); i++;do_stuff(i); i++;do_stuff(i); i++;do_stuff(i); i++;do_stuff(i); i++;do_stuff(i); i++;}

                                                                        可以看出,新代碼里比較指令由100次降低為10次,循環(huán)時間節(jié)約了90%。不過注意:對于中間變量或結果被更改的循環(huán),編譯程序往往拒絕展開,(怕?lián)熑螁h),這時候就需要你自己來做展開工作了。

                                                                        還有一點請注意,在有內部指令cache的CPU上(如MMX芯片),因為循環(huán)展開的代碼很大,往往cache溢出,這時展開的代碼會頻繁地在CPU?的cache和內存之間調來調去,又因為cache速度很高,所以此時循環(huán)展開反而會變慢。還有就是循環(huán)展開會影響矢量運算優(yōu)化。


                                                                        (6)、循環(huán)嵌套


                                                                        把相關循環(huán)放到一個循環(huán)里,也會加快速度。

                                                                        舊代碼:

                                                                          for (i = 0; i < MAX; i++) /* initialize 2d array to 0's */for (j = 0; j < MAX; j++)a[i][j] = 0.0;for (i = 0; i < MAX; i++) /* put 1's along the diagonal */a[i][i] = 1.0;


                                                                          新代碼:

                                                                            for (i = 0; i < MAX; i++) /* initialize 2d array to 0's */{ for (j = 0; j < MAX; j++)a[i][j] = 0.0;a[i][i] = 1.0; /* put 1's along the diagonal */}

                                                                            (7)、Switch語句中根據(jù)發(fā)生頻率來進行case排序


                                                                            Switch?可能轉化成多種不同算法的代碼。其中最常見的是跳轉表比較鏈/樹。當switch用比較鏈的方式轉化時,編譯器會產(chǎn)生if-else-if的嵌套代碼,并按照順序進行比較,匹配時就跳轉到滿足條件的語句執(zhí)行。所以可以對case的值依照發(fā)生的可能性進行排序,把最有可能的放在第一位,這樣可以提高性能。此外,在case中推薦使用小的連續(xù)的整數(shù),因為在這種情況下,所有的編譯器都可以把switch?轉化成跳轉表。

                                                                            不好的代碼:

                                                                              int days_in_month, short_months, normal_months, long_months;。。。。。。switch (days_in_month){   case 28:  case 29:    short_months ++;    break;  case 30:    normal_months ++;    break;  case 31:    long_months ++;    break;  default:    cout << "month has fewer than 28 or more than 31 days" << endl;    break;}


                                                                              推薦的代碼:

                                                                                int days_in_month, short_months, normal_months, long_months;。。。。。。switch (days_in_month){   case 31:    long_months ++;    break;  case 30:    normal_months ++;    break;  case 28:  case 29:    short_months ++;    break;  default:    cout << "month has fewer than 28 or more than 31 days" << endl;    break;}


                                                                                (8)、將大的switch語句轉為嵌套switch語句


                                                                                當switch語句中的case標號很多時,為了減少比較的次數(shù),明智的做法是把大switch語句轉為嵌套switch語句。把發(fā)生頻率高的case?標號放在一個switch語句中,并且是嵌套switch語句的最外層,發(fā)生相對頻率相對低的case標號放在另一個switch語句中。比如,下面的程序段把相對發(fā)生頻率低的情況放在缺省的case標號內。

                                                                                  pMsg=ReceiveMessage();switch (pMsg->type){ case FREQUENT_MSG1:handleFrequentMsg();break;case FREQUENT_MSG2:handleFrequentMsg2();break;。。。。。。case FREQUENT_MSGn:handleFrequentMsgn();break;default: //嵌套部分用來處理不經(jīng)常發(fā)生的消息switch (pMsg->type){ case INFREQUENT_MSG1:handleInfrequentMsg1();break;case INFREQUENT_MSG2:handleInfrequentMsg2();break;。。。。。。case INFREQUENT_MSGm:handleInfrequentMsgm();break;}}


                                                                                  如果switch中每一種情況下都有很多的工作要做,那么把整個switch語句用一個指向函數(shù)指針的表來替換會更加有效,比如下面的switch語句,有三種情況:

                                                                                    enum MsgType{ Msg1, Msg2, Msg3}switch (ReceiveMessage(){ case Msg1;。。。。。。case Msg2;。。。。。case Msg3;。。。。。}

                                                                                    為了提高執(zhí)行速度,用下面這段代碼來替換這個上面的switch語句。

                                                                                      /*準備工作*/int handleMsg1(void);int handleMsg2(void);int handleMsg3(void);/*創(chuàng)建一個函數(shù)指針數(shù)組*/int (*MsgFunction [])()={ handleMsg1, handleMsg2, handleMsg3};/*用下面這行更有效的代碼來替換switch語句*/status=MsgFunction[ReceiveMessage()]();


                                                                                      (9)、循環(huán)轉置


                                                                                      有些機器對JNZ(為0轉移)有特別的指令處理,速度非常快,如果你的循環(huán)對方向不敏感,可以由大向小循環(huán)。

                                                                                      舊代碼:

                                                                                        for (i = 1; i <= MAX; i++){ 。。。}


                                                                                        新代碼:

                                                                                          i = MAX+1;while (--i){ 。。。}

                                                                                          不過千萬注意,如果指針操作使用了i值,這種方法可能引起指針越界的嚴重錯誤(i = MAX+1;)。當然你可以通過對i做加減運算來糾正,但是這樣就起不到加速的作用,除非類似于以下情況:

                                                                                          舊代碼:

                                                                                            char a[MAX+5];for (i = 1; i <= MAX; i++){ *(a+i+4)=0;}


                                                                                            新代碼:

                                                                                              i = MAX+1;while (--i){ *(a+i+4)=0;}


                                                                                              (10)、公用代碼塊


                                                                                              一些公用處理模塊,為了滿足各種不同的調用需要,往往在內部采用了大量的if-then-else結構,這樣很不好,判斷語句如果太復雜,會消耗大量的時間的,應該盡量減少公用代碼塊的使用。(任何情況下,空間優(yōu)化和時間優(yōu)化都是對立的--東樓)。當然,如果僅僅是一個(3==x)之類的簡單判斷,適當使用一下,也還是允許的。記住,優(yōu)化永遠是追求一種平衡,而不是走極端。


                                                                                              (11)提升循環(huán)的性能


                                                                                              要提升循環(huán)的性能,減少多余的常量計算非常有用(比如,不隨循環(huán)變化的計算)。

                                                                                              不好的代碼(在for()中包含不變的if()):

                                                                                                for( i 。。。){   if( CONSTANT0 )  {     DoWork0( i );// 假設這里不改變CONSTANT0的值  }  else  {     DoWork1( i );// 假設這里不改變CONSTANT0的值  }}

                                                                                                推薦的代碼:

                                                                                                  if( CONSTANT0 ){   for( i 。。。)  {     DoWork0( i );  }}else{   for( i 。。。)  {     DoWork1( i );  }}


                                                                                                  如果已經(jīng)知道if()的值,這樣可以避免重復計算。雖然不好的代碼中的分支可以簡單地預測,但是由于推薦的代碼在進入循環(huán)前分支已經(jīng)確定,就可以減少對分支預測的依賴。


                                                                                                  (12)、選擇好的無限循環(huán)


                                                                                                  在編程中,我們常常需要用到無限循環(huán),常用的兩種方法是while (1)?和?for (;;)。這兩種方法效果完全一樣,但那一種更好呢?然我們看看它們編譯后的代碼:

                                                                                                  編譯前:

                                                                                                  while (1);

                                                                                                  編譯后:

                                                                                                  mov eax,1
                                                                                                  test eax,eax
                                                                                                  je foo+23h
                                                                                                  jmp foo+18h?

                                                                                                  編譯前:

                                                                                                  for (;;);

                                                                                                  編譯后:

                                                                                                  jmp foo+23h

                                                                                                  顯然,for (;;)指令少,不占用寄存器,而且沒有判斷、跳轉,比while (1)好。


                                                                                                  8、提高CPU的并行性


                                                                                                  (1)使用并行代碼


                                                                                                  盡可能把長的有依賴的代碼鏈分解成幾個可以在流水線執(zhí)行單元中并行執(zhí)行的沒有依賴的代碼鏈。很多高級語言,包括C++,并不對產(chǎn)生的浮點表達式重新排序,因為那是一個相當復雜的過程。需要注意的是,重排序的代碼和原來的代碼在代碼上一致并不等價于計算結果一致,因為浮點操作缺乏精確度。在一些情況下,這些優(yōu)化可能導致意料之外的結果。幸運的是,在大部分情況下,最后結果可能只有最不重要的位(即最低位)是錯誤的。

                                                                                                  不好的代碼:

                                                                                                    double a[100], sum;int i;sum = 0.0f;for (i=0;i<100;i++)sum += a[i];


                                                                                                    推薦的代碼:

                                                                                                      double a[100], sum1, sum2, sum3, sum4, sum;int i;sum1 = sum2 = sum3 = sum4 = 0.0;for (i = 0;i < 100;i += 4){   sum1 += a[i];  sum2 += a[i+1];  sum3 += a[i+2];  sum4 += a[i+3];}sum = (sum4+sum3)+(sum1+sum2); 

                                                                                                      要注意的是:使用4?路分解是因為這樣使用了4段流水線浮點加法,浮點加法的每一個段占用一個時鐘周期,保證了最大的資源利用率。


                                                                                                      (2)避免沒有必要的讀寫依賴


                                                                                                      當數(shù)據(jù)保存到內存時存在讀寫依賴,即數(shù)據(jù)必須在正確寫入后才能再次讀取。雖然AMD Athlon等CPU有加速讀寫依賴延遲的硬件,允許在要保存的數(shù)據(jù)被寫入內存前讀取出來,但是,如果避免了讀寫依賴并把數(shù)據(jù)保存在內部寄存器中,速度會更快。在一段很長的又互相依賴的代碼鏈中,避免讀寫依賴顯得尤其重要。如果讀寫依賴發(fā)生在操作數(shù)組時,許多編譯器不能自動優(yōu)化代碼以避免讀寫依賴。所以推薦程序員手動去消除讀寫依賴,舉例來說,引進一個可以保存在寄存器中的臨時變量。這樣可以有很大的性能提升。下面一段代碼是一個例子:

                                                                                                      不好的代碼:

                                                                                                        float x[VECLEN], y[VECLEN], z[VECLEN];。。。。。。for (unsigned int k = 1;k < VECLEN;k ++){   x[k] = x[k-1] + y[k];}for (k = 1;k {   x[k] = z[k] * (y[k] - x[k-1]);}

                                                                                                        推薦的代碼:

                                                                                                          float x[VECLEN], y[VECLEN], z[VECLEN];。。。。。。float t(x[0]);for (unsigned int k = 1;k < VECLEN;k ++){   t = t + y[k];  x[k] = t;}t = x[0];for (k = 1;k <;VECLEN;k ++){   t = z[k] * (y[k] - t);  x[k] = t;}


                                                                                                          10、循環(huán)不變計算


                                                                                                          對于一些不需要循環(huán)變量參加運算的計算任務可以把它們放到循環(huán)外面,現(xiàn)在許多編譯器還是能自己干這件事,不過對于中間使用了變量的算式它們就不敢動了,所以很多情況下你還得自己干。對于那些在循環(huán)中調用的函數(shù),凡是沒必要執(zhí)行多次的操作通通提出來,放到一個init函數(shù)里,循環(huán)前調用。另外盡量減少喂食次數(shù),沒必要的話盡量不給它傳參,需要循環(huán)變量的話讓它自己建立一個靜態(tài)循環(huán)變量自己累加,速度會快一點。

                                                                                                          還有就是結構體訪問,東樓的經(jīng)驗,凡是在循環(huán)里對一個結構體的兩個以上的元素執(zhí)行了訪問,就有必要建立中間變量了(結構這樣,那C++的對象呢?想想看),看下面的例子:

                                                                                                          舊代碼:

                                                                                                            total =a->b->c[4]->aardvark +a->b->c[4]->baboon +a->b->c[4]->cheetah +a->b->c[4]->dog;


                                                                                                            新代碼:

                                                                                                              struct animals * temp = a->b->c[4];total =temp->aardvark +temp->baboon +temp->cheetah +temp->dog;

                                                                                                              一些老的C語言編譯器不做聚合優(yōu)化,而符合ANSI規(guī)范的新的編譯器可以自動完成這個優(yōu)化,看例子:

                                                                                                                float a, b, c, d, f, g;。。。a = b / c * d;f = b * g / c;

                                                                                                                這種寫法當然要得,但是沒有優(yōu)化

                                                                                                                  float a, b, c, d, f, g;。。。a = b / c * d;f = b / c * g;
                                                                                                                  ?
                                                                                                                  如果這么寫的話,一個符合ANSI規(guī)范的新的編譯器可以只計算b/c一次,然后將結果代入第二個式子,節(jié)約了一次除法運算。


                                                                                                                  11、函數(shù)優(yōu)化

                                                                                                                  ?

                                                                                                                  (1)Inline函數(shù)


                                                                                                                  在C++中,關鍵字Inline可以被加入到任何函數(shù)的聲明中。這個關鍵字請求編譯器用函數(shù)內部的代碼替換所有對于指出的函數(shù)的調用。這樣做在兩個方面快于函數(shù)調用:第一,省去了調用指令需要的執(zhí)行時間;第二,省去了傳遞變元和傳遞過程需要的時間。但是使用這種方法在優(yōu)化程序速度的同時,程序長度變大了,因此需要更多的ROM。使用這種優(yōu)化在Inline函數(shù)頻繁調用并且只包含幾行代碼的時候是最有效的。


                                                                                                                  (2)不定義不使用的返回值


                                                                                                                  函數(shù)定義并不知道函數(shù)返回值是否被使用,假如返回值從來不會被用到,應該使用void來明確聲明函數(shù)不返回任何值。


                                                                                                                  (3)減少函數(shù)調用參數(shù)


                                                                                                                  使用全局變量比函數(shù)傳遞參數(shù)更加有效率。這樣做去除了函數(shù)調用參數(shù)入棧和函數(shù)完成后參數(shù)出棧所需要的時間。然而決定使用全局變量會影響程序的模塊化和重入,故要慎重使用。


                                                                                                                  (4)所有函數(shù)都應該有原型定義


                                                                                                                  一般來說,所有函數(shù)都應該有原型定義。原型定義可以傳達給編譯器更多的可能用于優(yōu)化的信息。


                                                                                                                  (5)盡可能使用常量(const)


                                                                                                                  盡可能使用常量(const)。C++?標準規(guī)定,如果一個const聲明的對象的地址不被獲取,允許編譯器不對它分配儲存空間。這樣可以使代碼更有效率,而且可以生成更好的代碼。


                                                                                                                  (6)把本地函數(shù)聲明為靜態(tài)的(static)


                                                                                                                  如果一個函數(shù)只在實現(xiàn)它的文件中被使用,把它聲明為靜態(tài)的(static)以強制使用內部連接。否則,默認的情況下會把函數(shù)定義為外部連接。這樣可能會影響某些編譯器的優(yōu)化——比如,自動內聯(lián)。


                                                                                                                  12、采用遞歸


                                                                                                                  與LISP之類的語言不同,C語言一開始就病態(tài)地喜歡用重復代碼循環(huán),許多C程序員都是除非算法要求,堅決不用遞歸。事實上,C編譯器們對優(yōu)化遞歸調用一點都不反感,相反,它們還很喜歡干這件事。只有在遞歸函數(shù)需要傳遞大量參數(shù),可能造成瓶頸的時候,才應該使用循環(huán)代碼,其他時候,還是用遞歸好些。

                                                                                                                  13、變量


                                                                                                                  (1)register變量


                                                                                                                  在聲明局部變量的時候可以使用register關鍵字。這就使得編譯器把變量放入一個多用途的寄存器中,而不是在堆棧中,合理使用這種方法可以提高執(zhí)行速度。函數(shù)調用越是頻繁,越是可能提高代碼的速度。

                                                                                                                  在最內層循環(huán)避免使用全局變量和靜態(tài)變量,除非你能確定它在循環(huán)周期中不會動態(tài)變化,大多數(shù)編譯器優(yōu)化變量都只有一個辦法,就是將他們置成寄存器變量,而對于動態(tài)變量,它們干脆放棄對整個表達式的優(yōu)化。盡量避免把一個變量地址傳遞給另一個函數(shù),雖然這個還很常用。C語言的編譯器們總是先假定每一個函數(shù)的變量都是內部變量,這是由它的機制決定的,在這種情況下,它們的優(yōu)化完成得最好。但是,一旦一個變量有可能被別的函數(shù)改變,這幫兄弟就再也不敢把變量放到寄存器里了,嚴重影響速度。看例子:

                                                                                                                  a = b();
                                                                                                                  c(&d);

                                                                                                                  因為d的地址被c函數(shù)使用,有可能被改變,編譯器不敢把它長時間的放在寄存器里,一旦運行到c(&d),編譯器就把它放回內存,如果在循環(huán)里,會造成N次頻繁的在內存和寄存器之間讀寫d的動作,眾所周知,CPU在系統(tǒng)總線上的讀寫速度慢得很。比如你的賽楊300,CPU主頻300,總線速度最多66M,為了一個總線讀,CPU可能要等4-5個周期,得。。得。。得。。想起來都打顫。


                                                                                                                  (2)、同時聲明多個變量優(yōu)于單獨聲明變量

                                                                                                                  (3)、短變量名優(yōu)于長變量名,應盡量使變量名短一點

                                                                                                                  (4)、在循環(huán)開始前聲明變量


                                                                                                                  14、使用嵌套的if結構


                                                                                                                  在if結構中如果要判斷的并列條件較多,最好將它們拆分成多個if結構,然后嵌套在一起,這樣可以避免無謂的判斷。


                                                                                                                  該方案主要是考慮到在嵌入式開發(fā)中對程序執(zhí)行速度的要求特別高,所以該方案主要是為了優(yōu)化程序的執(zhí)行速度

                                                                                                                  注意:優(yōu)化是有側重點的,優(yōu)化是一門平衡的藝術,它往往要以犧牲程序的可讀性或者增加代碼長度為代價。

                                                                                                                  15、盡量不要定義成全局變量

                                                                                                                  先來看一下局部變量、全局變量、靜態(tài)局部變量、靜態(tài)全局變量的異同。

                                                                                                                  ?局部變量:
                                                                                                                  在一個函數(shù)中或復合語句中定義的變量,在動態(tài)存儲區(qū)分配存儲單元,在調用時動態(tài)分配,在函數(shù)或復合語句結束時自動釋放。

                                                                                                                  ?靜態(tài)局部變量:
                                                                                                                  在一個函數(shù)中定義局部變量時,若加上static聲明,則此變量為靜態(tài)局部變量,在靜態(tài)存儲區(qū)分配存儲單元,在程序運行期間都不釋放;靜態(tài)局部變量只能在該函數(shù)中使用;靜態(tài)局部變量在編譯時賦值(若在定義時未進行賦值處理,則默認賦值為0(對數(shù)值型變量)或空字符(對字符型變量));靜態(tài)局部變量在函數(shù)調用結束后不自動釋放,保留函數(shù)調用結束后的值。

                                                                                                                  ?全局變量:
                                                                                                                  在函數(shù)外定義的變量稱為全局變量;全局變量在靜態(tài)存儲區(qū)分配存儲單元,在程序運行期間都不釋放,在文件中的函數(shù)均可調用該全局變量,其他文件內的函數(shù)調用全局變量,需加extern聲明。

                                                                                                                  ?靜態(tài)全局變量:
                                                                                                                  在函數(shù)外定義變量時,若加上staTIc聲明,則此變量為靜態(tài)全局變量;靜態(tài)全局變量在靜態(tài)存儲區(qū)分配存儲單元,在程序運行期間都不釋放,靜態(tài)全局變量在編譯時賦值(若在定義時未進行賦值處理,則默認賦值為0(對數(shù)值型變量)或空字符(對字符型變量));只能在當前文件中使用。

                                                                                                                  ?小結:
                                                                                                                  一般情況下就定義成局部變量,這樣不僅運行更高效,而且很方便移植。局部變量大多定位于MCU內部的寄存器中,在絕大多數(shù)MCU中,使用寄存器操作速度比數(shù)據(jù)存儲器快,指令也更多更靈活,有利于生成質量更高的代碼,而且局部變量所占用的寄存器和數(shù)據(jù)存儲器在不同的模塊中可以重復利用。

                                                                                                                  當中斷里需要用到的變量時,就需要定義成全局變量,并且加volaTIle修飾一下,防止編譯器優(yōu)化。如果數(shù)據(jù)是只讀的比如數(shù)碼管的斷碼、漢字取模的字庫需要放在ROM里,這樣可以節(jié)省RAM,51單片機是加code,高級點的單片機都是加const修飾。

                                                                                                                  免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

                                                                                                                  Popular articles

                                                                                                                  主站蜘蛛池模板: 小东西几天没做怎么这么多水| 日本高清免费xxx在线观看| 午夜精品在线免费观看| 国产无套乱子伦精彩是白视频| 扒开双腿疯狂进出爽爽爽动态图 | 欧美zoozzooz在线观看| 中文字幕不卡在线播放| 国产福利影院| 国产熟睡乱子伦视频| 波多野结衣大战三个黑鬼| 国产视频最新| 日本最新免费二区三区| 精品一区二区久久久久久久网站| 亚州不卡| 波多野结衣中文字幕电影| 亚洲乱码一二三四区国产| 制服丝袜电影在线观看| 37大但人文艺术a级| 日产2021乱码一区| 亚洲麻豆精品果冻传媒| 男朋友想吻我腿中间部位| 国产日韩欧美中文字幕| 亚洲同性男gay网站在线观看| 欧美草逼网站| 三级黄在线观看| 波多野结衣日本电影| 日日夜夜精品免费视频| 三级黄色录像片| 最近中文字幕免费4| 你懂的免费在线观看| 午夜爽| 中文字幕在线播放第一页| 老鸭窝在线免费视频| 久久99热66这里只有精品一| 午夜视频在线观看国产| 一本一本久久a久久精品综合| 动漫洗濯屋| 在车子颠簸中进了老师的身体| 欧美aaaaaaaa| 免费女人18毛片a级毛片视频 | 亚州不卡|