招生熱線
0755-86191118 0755-86191118
我的位置: 首頁(yè) > 學(xué)習(xí)專區(qū) > .NET技術(shù) > 教你如何優(yōu)化C代碼

教你如何優(yōu)化C代碼

2013-06-03 08:37:00
來(lái)源:
[導(dǎo)讀] 在性能優(yōu)化方面永遠(yuǎn)注意80-20原則,即20%的程序消耗了80%的運(yùn)行時(shí)間,因而我們要改進(jìn)效率,最主要是考慮改進(jìn)那20%的代碼。不要優(yōu)化程序中開

在性能優(yōu)化方面永遠(yuǎn)注意80-20原則,即20%的程序消耗了80%的運(yùn)行時(shí)間,因而我們要改進(jìn)效率,最主要是考慮改進(jìn)那20%的代碼。不要優(yōu)化程序中開銷不大的那80%,這是勞而無(wú)功的。

第一招:以空間換時(shí)間

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

方法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 ;

使用的時(shí)候可以直接用指針來(lái)操作。

從上面的例子可以看出,A和B的效率是不能比的。在同樣的存儲(chǔ)空間下,B直接使用指針就可以操作了,而A需要調(diào)用兩個(gè)字符函數(shù)才能完成。B的缺點(diǎn)在于靈活性沒(méi)有A好。在需要頻繁更改一個(gè)字符串內(nèi)容的時(shí)候,A具有更好的靈活性;如果采用方法B,則需要預(yù)存許多字符串,雖然占用了大量的內(nèi)存,但是獲得了程序執(zhí)行的高效率。

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

第二招: 使用宏而不是函數(shù)。

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

舉例如下:

方法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方法是我看到的最好的置位操作函數(shù),是arm公司源碼的一部分,在短短的三行內(nèi)實(shí)現(xiàn)了很多功能,幾乎涵蓋了所有的位操作功能。C方法是其變體,其中滋味還需大家仔細(xì)體會(huì)。

第三招:數(shù)學(xué)方法解決問(wèn)題

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

方法E:

int I , j;

for (I = 1 ;I<=100; I ++)

{

j += I;

}

方法F

int I;

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

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

第四招:使用位操作

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

方法G

int I,J;

I = 257 /8;

J = 456 % 32;

方法H

int I,J;

I = 257 >>3;

J = 456 - (456 >> 4 << 4);

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

對(duì)于以2的指數(shù)次方為"*"、"/"或"%"因子的數(shù)學(xué)運(yùn)算,轉(zhuǎn)化為移位運(yùn)算"<< >>"通常可以提高算法效率。因?yàn)槌顺\(yùn)算指令周期通常比移位運(yùn)算大。

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

#define INT_I2_MASK 0x0040

wTemp = inword(INT_MASK);

outword(INT_MASK, wTemp &~INT_I2_MASK);

而將該位設(shè)置為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 */

}

運(yùn)用這招需要注意的是,因?yàn)镃PU的不同而產(chǎn)生的問(wèn)題。比如說(shuō),在PC上用這招編寫的程序,并在PC上調(diào)試通過(guò),在移植到一個(gè)16位機(jī)平臺(tái)上的時(shí)候,可能會(huì)產(chǎn)生代碼隱患。所以只有在一定技術(shù)進(jìn)階的基礎(chǔ)下才可以使用這招。

第五招:匯編嵌入

在熟悉匯編語(yǔ)言的人眼里,C語(yǔ)言編寫的程序都是垃圾"。這種說(shuō)法雖然偏激了一些,但是卻有它的道理。匯編語(yǔ)言是效率最高的計(jì)算機(jī)語(yǔ)言,但是,不可能靠著它來(lái)寫一個(gè)操作系統(tǒng)吧?所以,為了獲得程序的高效率,我們只好采用變通的方法--嵌入?yún)R編,混合編程。嵌入式C程序中主要使用在線匯編,即在C程序中直接插入_asm{ }內(nèi)嵌匯編語(yǔ)句。

舉例如下,將數(shù)組一賦值給數(shù)組二,要求每一字節(jié)都相符。

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

再舉個(gè)例子:

/* 把兩個(gè)輸入?yún)?shù)的值相加,結(jié)果存放到另外一個(gè)全局變量中 */

int result;

void Add(long a, long *b)

{

_asm

{

MOV AX, a

MOV BX, b

ADD AX, [BX]

MOV result, AX

}

}

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

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

第六招, 使用寄存器變量

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

(1) 只有局部自動(dòng)變量和形參才可以定義為寄存器變量。因?yàn)榧拇嫫髯兞繉儆趧?dòng)態(tài)存儲(chǔ)方式,凡需要采用靜態(tài)存儲(chǔ)方式的量都不能定義為寄存器變量,包括:模塊間全局變量、模塊內(nèi)全局變量、局部static變量;

(2) register是一個(gè)"建議"型關(guān)鍵字,意指程序建議該變量放在寄存器中,但最終該變量可能因?yàn)闂l件不滿足并未成為寄存器變量,而是被放在了存儲(chǔ)器中,但編譯器中并不報(bào)錯(cuò)(在C++語(yǔ)言中有另一個(gè)"建議"型關(guān)鍵字:inline)。

下面是一個(gè)采用寄存器變量的例子:

/* 求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都被頻繁使用,因此可定義為寄存器變量。

第七招: 利用硬件特性

首先要明白CPU對(duì)各種存儲(chǔ)器的訪問(wèn)速度,基本上是:

CPU內(nèi)部RAM > 外部同步RAM > 外部異步RAM > FLASH/ROM

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

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

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

深圳北大青鳥

評(píng)論
好吊妞免费视频在线观看,久久亚洲国产人成综合网,久久精品国产2020,欧美精品综合在线
中文字幕在线九热 | 永久免费A在线观看全网站 亚洲日韩AV在线不卡 | 中文字幕亚洲国产精品 | 日本乱妇AⅤ在线观看 | 真实国产乱子伦精品免费视频 | 亚洲中文有码字幕日本 |