提高C++程序運(yùn)行效率的10個(gè)簡(jiǎn)單方法
本文以C/C++程序?yàn)槔v述了程序運(yùn)行效率的10個(gè)簡(jiǎn)單方法,分享給大家供大家參考之用。具體分析如下:
對(duì)于每一個(gè)程序員來(lái)說(shuō),程序的運(yùn)行效率都是一個(gè)值得重視,并為之付出努力的問(wèn)題。但是程序性能的優(yōu)化也是一門(mén)復(fù)雜的學(xué)問(wèn),需要很多的知識(shí),然而并不是每個(gè)程序員都具備這樣的知識(shí),而且論述如何優(yōu)化程序提高程序運(yùn)行效率的書(shū)籍也很少。但是這并不等于我們可以忽略程序的運(yùn)行效率,下面就介紹一下本人積累的一些簡(jiǎn)單實(shí)用的提高程序運(yùn)行效率的方法,希望對(duì)大家有所幫助。
一、盡量減少值傳遞,多用引用來(lái)傳遞參數(shù)。
至于其中的原因,相信大家也很清楚,如果參數(shù)是int等語(yǔ)言自定義的類(lèi)型可能能性能的影響還不是很大,但是如果參數(shù)是一個(gè)類(lèi)的對(duì)象,那么其效率問(wèn)題就不言而喻了。例如一個(gè)判斷兩個(gè)字符串是否相等的函數(shù),其聲明如下:
bool Compare(string s1, string s2) bool Compare(string *s1, string *s2) bool Compare(string &s1, string &s2) bool Compare(const string &s1, const string &s2)
其中若使用第一個(gè)函數(shù)(值傳遞),則在參數(shù)傳遞和函數(shù)返回時(shí),需要調(diào)用string的構(gòu)造函數(shù)和析構(gòu)函數(shù)兩次(即共多調(diào)用了四個(gè)函數(shù)),而其他的三個(gè)函數(shù)(指針傳遞和引用傳遞)則不需要調(diào)用這四個(gè)函數(shù)。因?yàn)橹羔樅鸵枚疾粫?huì)創(chuàng)建新的對(duì)象。如果一個(gè)構(gòu)造一個(gè)對(duì)象和析構(gòu)一個(gè)對(duì)象的開(kāi)銷(xiāo)是龐大的,這就是會(huì)效率造成一定的影響。
然而在很多人的眼中,指針是一個(gè)惡夢(mèng),使用指針就意味著錯(cuò)誤,那么就使用引用吧!它與使用普通值傳遞一樣方便直觀,同時(shí)具有指針傳遞的高效和能力。因?yàn)橐檬且粋€(gè)變量的別名,對(duì)其操作等同于對(duì)實(shí)際對(duì)象操作,所以當(dāng)你確定在你的函數(shù)是不會(huì)或不需要變量參數(shù)的值時(shí),就大膽地在聲明的前面加上一個(gè)const吧,就如最后的一個(gè)函數(shù)聲明一樣。
同時(shí)加上一個(gè)const還有一個(gè)好處,就是可以對(duì)常量進(jìn)行引用,若不加上const修飾符,引用是不能引用常量的。
二、++i和i++引申出的效率問(wèn)題
看了上面的第一點(diǎn),你可能覺(jué)得,那不就是多調(diào)用了四個(gè)函數(shù)而已,你可能對(duì)此不屑一顧。那么來(lái)看看下面的例子,應(yīng)該會(huì)讓你大吃一驚。
至于整型變量的前加和后加的區(qū)別相信大家也是很清楚的。然而在這里我想跟大家談的卻是C++類(lèi)的運(yùn)算符重載,為了與整形變量的用法一致,在C++中重載運(yùn)算符++時(shí)一般都會(huì)把前加和后加都重載。你可能會(huì)說(shuō),你在代碼中不會(huì)重載++運(yùn)算符,但是你敢說(shuō)你沒(méi)有使用過(guò)類(lèi)的++運(yùn)算符重載嗎?迭代器類(lèi)你總使用過(guò)吧!可能到現(xiàn)在你還不是很懂我在說(shuō)什么,那么就先看看下面的例子吧,是本人為鏈表寫(xiě)的一個(gè)內(nèi)部迭代器。
_SingleList::Iterator& _SingleList::Iterator::operator++()//前加
{
pNote = pNote->pNext;
return *this;
}
_SingleList::Iterator _SingleList::Iterator::operator++(int)//后加
{
Iterator tmp(*this);
pNote = pNote->pNext;
return tmp;
}
從后加的實(shí)現(xiàn)方式可以知道,對(duì)象利用自己創(chuàng)建一個(gè)臨時(shí)對(duì)象(自己在函數(shù)調(diào)用的一個(gè)復(fù)制),然后改變自己的狀態(tài),并返回這個(gè)臨時(shí)對(duì)象,而前加的實(shí)現(xiàn)方式時(shí),直接改變自己的內(nèi)部狀態(tài),并返回自己的引用。
從第一點(diǎn)的論述可以知道后加實(shí)現(xiàn)時(shí)會(huì)調(diào)用復(fù)制構(gòu)造函數(shù),在函數(shù)返回時(shí)還要調(diào)用析構(gòu)函數(shù),而由于前加實(shí)現(xiàn)方式直接改變對(duì)象的內(nèi)部狀態(tài),并返回自己的引用,至始至終也沒(méi)有創(chuàng)建新的對(duì)象,所以也就不會(huì)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。
然而更加糟糕的是,迭代器通常是用來(lái)遍歷容器的,它大多應(yīng)用在循環(huán)中,試想你的鏈表有100個(gè)元素,用下面的兩種方式遍歷:
for(_SingleList::Iterator it = list.begin(); it != list.end(); ++it)
{
//do something
}
for(_SingleList::Iterator it = list.begin(); it != list.end(); it++)
{
//do something
}
如果你的習(xí)慣不好,寫(xiě)了第二種形式,那么很不幸,做同樣的事情,就是因?yàn)橐粋€(gè)前加和一個(gè)后加的區(qū)別,你就要調(diào)用多200個(gè)函數(shù),其對(duì)效率的影響可就不可忽視了。
三、循環(huán)引發(fā)的討論1(循環(huán)內(nèi)定義,還是循環(huán)外定義對(duì)象)
請(qǐng)看下面的兩段代碼:
代碼1:
ClassTest CT;
for(int i = 0; i < 100; ++i)
{
CT = a;
//do something
}
代碼2:
for(int i = 0; i < 100; ++i)
{
ClassTest CT = a;
//do something
}
你會(huì)覺(jué)得哪段代碼的運(yùn)行效率較高呢?代碼1科學(xué)家是代碼2?其實(shí)這種情況下,哪段代碼的效率更高是不確定的,或者說(shuō)是由這個(gè)類(lèi)ClassTest本向決定的,分析如下:
對(duì)于代碼1:需要調(diào)用ClassTest的構(gòu)造函數(shù)1次,賦值操作函數(shù)(operator=)100次;對(duì)于代碼2:需要高用(復(fù)制)構(gòu)造函數(shù)100次,析構(gòu)函數(shù)100次。
如果調(diào)用賦值操作函數(shù)的開(kāi)銷(xiāo)比調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)的總開(kāi)銷(xiāo)小,則第一種效率高,否則第二種的效率高。
四、循環(huán)引發(fā)的討論2(避免過(guò)大的循環(huán))
現(xiàn)在請(qǐng)看下面的兩段代碼,
代碼1:
for(int i = 0; i < n; ++i)
{
fun1();
fun2();
}
代碼2:
for(int i = 0; i < n; ++i)
{
fun1();
}
for(int i = 0; i < n; ++i)
{
fun2();
}
注:這里的fun1()和fun2()是沒(méi)有關(guān)聯(lián)的,即兩段代碼所產(chǎn)生的結(jié)果是一樣的。
以代碼的層面上來(lái)看,似乎是代碼1的效率更高,因?yàn)楫吘勾a1少了n次的自加運(yùn)算和判斷,畢竟自加運(yùn)算和判斷也是需要時(shí)間的。但是現(xiàn)實(shí)真的是這樣嗎?
這就要看fun1和fun2這兩個(gè)函數(shù)的規(guī)模(或復(fù)雜性)了,如果這多個(gè)函數(shù)的代碼語(yǔ)句很少,則代碼1的運(yùn)行效率高一些,但是若fun1和fun2的語(yǔ)句有很多,規(guī)模較大,則代碼2的運(yùn)行效率會(huì)比代碼1顯著高得多??赡苣悴幻靼走@是為什么,要說(shuō)是為什么這要由計(jì)算機(jī)的硬件說(shuō)起。
由于CPU只能從內(nèi)存在讀取數(shù)據(jù),而CPU的運(yùn)算速度遠(yuǎn)遠(yuǎn)大于內(nèi)存,所以為了提高程序的運(yùn)行速度有效地利用CPU的能力,在內(nèi)存與CPU之間有一個(gè)叫Cache的存儲(chǔ)器,它的速度接近CPU。而Cache中的數(shù)據(jù)是從內(nèi)存中加載而來(lái)的,這個(gè)過(guò)程需要訪問(wèn)內(nèi)存,速度較慢。
這里先說(shuō)說(shuō)Cache的設(shè)計(jì)原理,就是時(shí)間局部性和空間局部性。時(shí)間局部性是指如果一個(gè)存儲(chǔ)單元被訪問(wèn),則可能該單元會(huì)很快被再次訪問(wèn),這是因?yàn)槌绦虼嬖谥h(huán)??臻g局部性是指如果一個(gè)儲(chǔ)存單元被訪問(wèn),則該單元鄰近的單元也可能很快被訪問(wèn),這是因?yàn)槌绦蛑写蟛糠种噶钍琼樞虼鎯?chǔ)、順序執(zhí)行的,數(shù)據(jù)也一般也是以向量、數(shù)組、樹(shù)、表等形式簇聚在一起的。
看到這里你可能已經(jīng)明白其中的原因了。沒(méi)錯(cuò),就是這樣!如果fun1和fun2的代碼量很大,例如都大于Cache的容量,則在代碼1中,就不能充分利用Cache了(由時(shí)間局部性和空間局部性可知),因?yàn)槊垦h(huán)一次,都要把Cache中的內(nèi)容踢出,重新從內(nèi)存中加載另一個(gè)函數(shù)的代碼指令和數(shù)據(jù),而代碼2則更很好地利用了Cache,利用兩個(gè)循環(huán)語(yǔ)句,每個(gè)循環(huán)所用到的數(shù)據(jù)幾乎都已加載到Cache中,每次循環(huán)都可從Cache中讀寫(xiě)數(shù)據(jù),訪問(wèn)內(nèi)存較少,速度較快,理論上來(lái)說(shuō)只需要完全踢出fun1的數(shù)據(jù)1次即可。
五、局部變量VS靜態(tài)變量
很多人認(rèn)為局部變量在使用到時(shí)才會(huì)在內(nèi)存中分配儲(chǔ)存單元,而靜態(tài)變量在程序的一開(kāi)始便存在于內(nèi)存中,所以使用靜態(tài)變量的效率應(yīng)該比局部變量高,其實(shí)這是一個(gè)誤區(qū),使用局部變量的效率比使用靜態(tài)變量要高。
這是因?yàn)榫植孔兞渴谴嬖谟诙褩V械?,?duì)其空間的分配僅僅是修改一次esp寄存器的內(nèi)容即可(即使定義一組局部變量也是修改一次)。而局部變量存在于堆棧中最大的好處是,函數(shù)能重復(fù)使用內(nèi)存,當(dāng)一個(gè)函數(shù)調(diào)用完畢時(shí),退出程序堆棧,內(nèi)存空間被回收,當(dāng)新的函數(shù)被調(diào)用時(shí),局部變量又可以重新使用相同的地址。當(dāng)一塊數(shù)據(jù)被反復(fù)讀寫(xiě),其數(shù)據(jù)會(huì)留在CPU的一級(jí)緩存(Cache)中,訪問(wèn)速度非??臁6o態(tài)變量卻不存在于堆棧中。
可以說(shuō)靜態(tài)變量是低效的。
六、避免使用多重繼承
在C++中,支持多繼承,即一個(gè)子類(lèi)可以有多個(gè)父類(lèi)。書(shū)上都會(huì)跟我們說(shuō),多重繼承的復(fù)雜性和使用的困難,并告誡我們不要輕易使用多重繼承。其實(shí)多重繼承并不僅僅使程序和代碼變得更加復(fù)雜,還會(huì)影響程序的運(yùn)行效率。
這是因?yàn)樵贑++中每個(gè)對(duì)象都有一個(gè)this指針指向?qū)ο蟊旧恚鳦++中類(lèi)對(duì)成員變量的使用是通過(guò)this的地址加偏移量來(lái)計(jì)算的,而在多重繼承的情況下,這個(gè)計(jì)算會(huì)變量更加復(fù)雜,從而降低程序的運(yùn)行效率。而為了解決二義性,而使用虛基類(lèi)的多重繼承對(duì)效率的影響更為嚴(yán)重,因?yàn)槠淅^承關(guān)系更加復(fù)雜和成員變量所屬的父類(lèi)關(guān)系更加復(fù)雜。
七、盡量少使用dynamic_cast
dynamic_cast的作用是進(jìn)行指針或引用的類(lèi)型轉(zhuǎn)換,dynamic_cast的轉(zhuǎn)換需要目標(biāo)類(lèi)型和源對(duì)象有一定的關(guān)系:繼承關(guān)系。 實(shí)現(xiàn)從子類(lèi)到基類(lèi)的指針轉(zhuǎn)換,實(shí)際上這種轉(zhuǎn)換是非常低效的,對(duì)程序的性能影響也比較大,不可大量使用,而且繼承關(guān)系越復(fù)雜,層次越深,其轉(zhuǎn)換時(shí)間開(kāi)銷(xiāo)越大。在程序中應(yīng)該盡量減少使用。
八、減少除法運(yùn)算的使用
無(wú)論是整數(shù)還是浮點(diǎn)數(shù)運(yùn)算,除法都是一件運(yùn)算速度很慢的指令,在計(jì)算機(jī)中實(shí)現(xiàn)除法是比較復(fù)雜的。所以要減少除法運(yùn)算的次數(shù),下面介紹一些簡(jiǎn)單方法來(lái)提高效率:
1、通過(guò)數(shù)學(xué)的方法,把除法變?yōu)槌朔ㄟ\(yùn)算,如if(a > b/c),如果a、b、c都是正數(shù),則可寫(xiě)成if(a*c > b)
2、讓編譯器有優(yōu)化的余地,如里你要做的運(yùn)算是int型的n/8的話,寫(xiě)成(unsigned)n/8有利于編譯器的優(yōu)化。而要讓編譯器有優(yōu)化的余地,則除數(shù)必須為常數(shù),而這也可以用const修飾一個(gè)變量來(lái)達(dá)到目的。
九、將小粒度函數(shù)聲明為內(nèi)聯(lián)函數(shù)(inline)
正如我們所知,調(diào)用函數(shù)是需要保護(hù)現(xiàn)場(chǎng),為局部變量分配內(nèi)存,函數(shù)結(jié)束后還要恢復(fù)現(xiàn)場(chǎng)等開(kāi)銷(xiāo),而內(nèi)聯(lián)函數(shù)則是把它的代碼直接寫(xiě)到調(diào)用函數(shù)處,所以不需要這些開(kāi)銷(xiāo),但會(huì)使程序的源代碼長(zhǎng)度變大。
所以若是小粒度的函數(shù),如下面的Max函數(shù),由于不需要調(diào)用普通函數(shù)的開(kāi)銷(xiāo),所以可以提高程序的效率。
int Max(int a, int b)
{
return a>b?a:b;
}
十、多用直接初始化
與直接初始化對(duì)應(yīng)的是復(fù)制初始化,什么是直接初始化?什么又是復(fù)制初始化?舉個(gè)簡(jiǎn)單的例子,
ClassTest ct1; ClassTest ct2(ct1); //直接初始化 ClassTest ct3 = ct1; //復(fù)制初始化
那么直接初始化與復(fù)制初始化又有什么不同呢?直接初始化是直接以一個(gè)對(duì)象來(lái)構(gòu)造另一個(gè)對(duì)象,如用ct1來(lái)構(gòu)造ct2,復(fù)制初始化是先構(gòu)造一個(gè)對(duì)象,再把另一個(gè)對(duì)象值復(fù)制給這個(gè)對(duì)象,如先構(gòu)造一個(gè)對(duì)象ct3,再把ct1中的成員變量的值復(fù)制給ct3,從這里,可以看出直接初始化的效率更高一點(diǎn),而且使用直接初始化還是一個(gè)好處,就是對(duì)于不能進(jìn)行復(fù)制操作的對(duì)象,如流對(duì)象,是不能使用賦值初始化的,只能進(jìn)行直接初始化。可能我說(shuō)得不太清楚,那么下面就引用一下經(jīng)典吧!
以下是Primer是的原話:
“當(dāng)用于類(lèi)類(lèi)型對(duì)象時(shí),初始化的復(fù)制形式和直接形式有所不同:直接初始化直接調(diào)用與實(shí)參匹配的構(gòu)造函數(shù),復(fù)制初始化總是調(diào)用復(fù)制構(gòu)造函數(shù)。復(fù)制初始化首先使用指定構(gòu)造函數(shù)創(chuàng)建一個(gè)臨時(shí)對(duì)象,然后用復(fù)制構(gòu)造函數(shù)將那個(gè)臨時(shí)對(duì)象復(fù)制到正在創(chuàng)建的對(duì)象”,還有一段這樣說(shuō),“通常直接初始化和復(fù)制初始化僅在低級(jí)別優(yōu)化上存在差異,然而,對(duì)于不支持復(fù)制的類(lèi)型,或者使用非explicit構(gòu)造函數(shù)的時(shí)候,它們有本質(zhì)區(qū)別:
ifstream file1("filename")://ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private
”
注:如還對(duì)直接初始化和復(fù)制初始化有疑問(wèn),可以參考一下前面的一篇文章:
C++直接初始化與復(fù)制初始化的區(qū)別深入解析,里面有有關(guān)直接初始化和復(fù)制初始化的詳細(xì)解釋。
補(bǔ)充:
這里只是一點(diǎn)點(diǎn)的建議,雖然說(shuō)了這么多,但是還是要說(shuō)一下的就是:要避免不必要的優(yōu)化,避免不成熟的優(yōu)化,不成熟的優(yōu)化的是錯(cuò)誤的來(lái)源,因?yàn)榫幾g器會(huì)為你做很多你所不知道的優(yōu)化。
希望本文所述對(duì)提高大家C++程序設(shè)計(jì)效率能有所幫助。
相關(guān)文章
C++聲明extern變量和extern函數(shù)的用法
extern關(guān)鍵字可以用來(lái)聲明變量和函數(shù)作為外部變量或者外部函數(shù)供其它文件使用,所以本文給大家介紹了C++聲明extern變量和extern函數(shù)的用法,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-11-11
C/C++?QT實(shí)現(xiàn)自定義對(duì)話框的示例代碼
對(duì)話框分為多種,常見(jiàn)的有通用對(duì)話框,自定義對(duì)話框,模態(tài)對(duì)話框,非模態(tài)對(duì)話框等,本文主要介紹了QT自定義對(duì)話框,感興趣的可以了解一下2021-11-11
詳解Bucket Sort桶排序算法及C++代碼實(shí)現(xiàn)示例
桶排序是一種線性排序算法,這里我們來(lái)詳解Bucket Sort桶排序算法及C++代碼實(shí)現(xiàn)示例,需要的朋友可以參考下2016-07-07
使用c語(yǔ)言判斷100以?xún)?nèi)素?cái)?shù)的示例(c語(yǔ)言求素?cái)?shù))
這篇文章主要介紹了使用c語(yǔ)言判斷100以?xún)?nèi)素?cái)?shù)的示例(c語(yǔ)言求素?cái)?shù)),需要的朋友可以參考下2014-03-03
C++中的Lambda表達(dá)式及表達(dá)式語(yǔ)句
這篇文章主要介紹了C++中的Lambda表達(dá)式及表達(dá)式語(yǔ)句,表達(dá)式這個(gè)概念在C++中屬于比較細(xì)節(jié)的知識(shí)了,很多時(shí)候我們只用知道怎么用,對(duì)于編譯器內(nèi)部怎么處理我們并不關(guān)心;并且關(guān)于左值和右值這個(gè)概念,也是C++比較深的一個(gè)小知識(shí)點(diǎn),需要的朋友可以參考一下2021-12-12
簡(jiǎn)單了解C++常見(jiàn)編程問(wèn)題解決方案
這篇文章主要介紹了C++常見(jiàn)編程問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
C++無(wú)痛實(shí)現(xiàn)日期類(lèi)的示例代碼
凡是要寫(xiě)類(lèi)必須要提到六大默認(rèn)成員(六位大爺):構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、賦值重載函數(shù)、取地址重載函數(shù)(包括const對(duì)象和普通對(duì)象);那么這次的日期類(lèi)又需要伺候哪幾位大爺呢?本文就來(lái)詳細(xì)說(shuō)說(shuō)2022-10-10
strcat函數(shù)實(shí)現(xiàn)簡(jiǎn)單示例
這篇文章主要介紹了strcat函數(shù)實(shí)現(xiàn)簡(jiǎn)單示例,需要的朋友可以參考下2014-03-03

