對于每一個(gè)程序員來說,程序的運(yùn)行效率都是一個(gè)值得重視,并為之付出努力的問題。但是程序性能的優(yōu)化也是一門復(fù)雜的學(xué)問,需要很多的知識(shí),然而并不是每個(gè)程序員都具備這樣的知識(shí),而且論述如何優(yōu)化程序提高程序運(yùn)行效率的書籍也很少。但是這并不等于我們可以忽略程序的運(yùn)行效率,下面就介紹一下本人積累的一些簡單實(shí)用的提高程序運(yùn)行效率的方法,希望對大家有所幫助。
一、盡量減少值傳遞,多用引用來傳遞參數(shù)。 至于其中的原因,相信大家也很清楚,如果參數(shù)是int等語言自定義的類型可能能性能的影響還不是很大,但是如果參數(shù)是一個(gè)類的對象,那么其效率問題就不言而喻了。例如一個(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)建新的對象。如果一個(gè)構(gòu)造一個(gè)對象和析構(gòu)一個(gè)對象的開銷是龐大的,這就是會(huì)效率造成一定的影響。
然而在很多人的眼中,指針是一個(gè)惡夢,使用指針就意味著錯(cuò)誤,那么就使用引用吧!它與使用普通值傳遞一樣方便直觀,同時(shí)具有指針傳遞的高效和能力。因?yàn)橐檬且粋€(gè)變量的別名,對其操作等同于對實(shí)際對象操作,所以當(dāng)你確定在你的函數(shù)是不會(huì)或不需要變量參數(shù)的值時(shí),就大膽地在聲明的前面加上一個(gè)const吧,就如最后的一個(gè)函數(shù)聲明一樣。 同時(shí)加上一個(gè)const還有一個(gè)好處,就是可以對常量進(jìn)行引用,若不加上const修飾符,引用是不能引用常量的。
二、++i和i++引申出的效率問題 看了上面的第一點(diǎn),你可能覺得,那不就是多調(diào)用了四個(gè)函數(shù)而已,你可能對此不屑一顧。那么來看看下面的例子,應(yīng)該會(huì)讓你大吃一驚。 至于整型變量的前加和后加的區(qū)別相信大家也是很清楚的。然而在這里我想跟大家談的卻是C++類的運(yùn)算符重載,為了與整形變量的用法一致,在C++中重載運(yùn)算符++時(shí)一般都會(huì)把前加和后加都重載。你可能會(huì)說,你在代碼中不會(huì)重載++運(yùn)算符,但是你敢說你沒有使用過類的++運(yùn)算符重載嗎?迭代器類你總使用過吧!可能到現(xiàn)在你還不是很懂我在說什么,那么就先看看下面的例子吧,是本人為鏈表寫的一個(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)方式可以知道,對象利用自己創(chuàng)建一個(gè)臨時(shí)對象(自己在函數(shù)調(diào)用的一個(gè)復(fù)制),然后改變自己的狀態(tài),并返回這個(gè)臨時(shí)對象,而前加的實(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)方式直接改變對象的內(nèi)部狀態(tài),并返回自己的引用,至始至終也沒有創(chuàng)建新的對象,所以也就不會(huì)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。 然而更加糟糕的是,迭代器通常是用來遍歷容器的,它大多應(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í)慣不好,寫了第二種形式,那么很不幸,做同樣的事情,就是因?yàn)橐粋€(gè)前加和一個(gè)后加的區(qū)別,你就要調(diào)用多200個(gè)函數(shù),其對效率的影響可就不可忽視了。
三、循環(huán)引發(fā)的討論1(循環(huán)內(nèi)定義,還是循環(huán)外定義對象) 請看下面的兩段代碼: 代碼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ì)覺得哪段代碼的運(yùn)行效率較高呢?代碼1科學(xué)家是代碼2?其實(shí)這種情況下,哪段代碼的效率更高是不確定的,或者說是由這個(gè)類ClassTest本向決定的,分析如下: 對于代碼1:需要調(diào)用ClassTest的構(gòu)造函數(shù)1次,賦值操作函數(shù)(operator=)100次;對于代碼2:需要高用(復(fù)制)構(gòu)造函數(shù)100次,析構(gòu)函數(shù)100次。 如果調(diào)用賦值操作函數(shù)的開銷比調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)的總開銷小,則第一種效率高,否則第二種的效率高。
四、循環(huán)引發(fā)的討論2(避免過大的循環(huán)) 現(xiàn)在請看下面的兩段代碼, 代碼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()是沒有關(guān)聯(lián)的,即兩段代碼所產(chǎn)生的結(jié)果是一樣的。以代碼的層面上來看,似乎是代碼1的效率更高,因?yàn)楫吘勾a1少了n次的自加運(yùn)算和判斷,畢竟自加運(yùn)算和判斷也是需要時(shí)間的。但是現(xiàn)實(shí)真的是這樣嗎? 這就要看fun1和fun2這兩個(gè)函數(shù)的規(guī)模(或復(fù)雜性)了,如果這多個(gè)函數(shù)的代碼語句很少,則代碼1的運(yùn)行效率高一些,但是若fun1和fun2的語句有很多,規(guī)模較大,則代碼2的運(yùn)行效率會(huì)比代碼1顯著高得多。可能你不明白這是為什么,要說是為什么這要由計(jì)算機(jī)的硬件說起。 由于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)存中加載而來的,這個(gè)過程需要訪問內(nèi)存,速度較慢。 這里先說說Cache的設(shè)計(jì)原理,就是時(shí)間局部性和空間局部性。時(shí)間局部性是指如果一個(gè)存儲(chǔ)單元被訪問,則可能該單元會(huì)很快被再次訪問,這是因?yàn)槌绦虼嬖谥h(huán)??臻g局部性是指如果一個(gè)儲(chǔ)存單元被訪問,則該單元鄰近的單元也可能很快被訪問,這是因?yàn)槌绦蛑写蟛糠种噶钍琼樞虼鎯?chǔ)、順序執(zhí)行的,數(shù)據(jù)也一般也是以向量、數(shù)組、樹、表等形式簇聚在一起的。 看到這里你可能已經(jīng)明白其中的原因了。沒錯(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)語句,每個(gè)循環(huán)所用到的數(shù)據(jù)幾乎都已加載到Cache中,每次循環(huán)都可從Cache中讀寫數(shù)據(jù),訪問內(nèi)存較少,速度較快,理論上來說只需要完全踢出fun1的數(shù)據(jù)1次即可。
五、局部變量VS靜態(tài)變量 很多人認(rèn)為局部變量在使用到時(shí)才會(huì)在內(nèi)存中分配儲(chǔ)存單元,而靜態(tài)變量在程序的一開始便存在于內(nèi)存中,所以使用靜態(tài)變量的效率應(yīng)該比局部變量高,其實(shí)這是一個(gè)誤區(qū),使用局部變量的效率比使用靜態(tài)變量要高。 這是因?yàn)榫植孔兞渴谴嬖谟诙褩V械?,對其空間的分配僅僅是修改一次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ù)讀寫,其數(shù)據(jù)會(huì)留在CPU的一級緩存(Cache)中,訪問速度非???。而靜態(tài)變量卻不存在于堆棧中。 可以說靜態(tài)變量是低效的。
六、避免使用多重繼承 在C++中,支持多繼承,即一個(gè)子類可以有多個(gè)父類。書上都會(huì)跟我們說,多重繼承的復(fù)雜性和使用的困難,并告誡我們不要輕易使用多重繼承。其實(shí)多重繼承并不僅僅使程序和代碼變得更加復(fù)雜,還會(huì)影響程序的運(yùn)行效率。 這是因?yàn)樵贑++中每個(gè)對象都有一個(gè)this指針指向?qū)ο蟊旧?,而C++中類對成員變量的使用是通過this的地址加偏移量來計(jì)算的,而在多重繼承的情況下,這個(gè)計(jì)算會(huì)變量更加復(fù)雜,從而降低程序的運(yùn)行效率。而為了解決二義性,而使用虛基類的多重繼承對效率的影響更為嚴(yán)重,因?yàn)槠淅^承關(guān)系更加復(fù)雜和成員變量所屬的父類關(guān)系更加復(fù)雜。
七、盡量少使用dynamic_cast dynamic_cast的作用是進(jìn)行指針或引用的類型轉(zhuǎn)換,dynamic_cast的轉(zhuǎn)換需要目標(biāo)類型和源對象有一定的關(guān)系:繼承關(guān)系。 實(shí)現(xiàn)從子類到基類的指針轉(zhuǎn)換,實(shí)際上這種轉(zhuǎn)換是非常低效的,對程序的性能影響也比較大,不可大量使用,而且繼承關(guān)系越復(fù)雜,層次越深,其轉(zhuǎn)換時(shí)間開銷越大。在程序中應(yīng)該盡量減少使用。
八、減少除法運(yùn)算的使用 無論是整數(shù)還是浮點(diǎn)數(shù)運(yùn)算,除法都是一件運(yùn)算速度很慢的指令,在計(jì)算機(jī)中實(shí)現(xiàn)除法是比較復(fù)雜的。所以要減少除法運(yùn)算的次數(shù),下面介紹一些簡單方法來提高效率: 1、通過數(shù)學(xué)的方法,把除法變?yōu)槌朔ㄟ\(yùn)算,如if(a > b/c),如果a、b、c都是正數(shù),則可寫成if(a*c > b) 2、讓編譯器有優(yōu)化的余地,如里你要做的運(yùn)算是int型的n/8的話,寫成(unsigned)n/8有利于編譯器的優(yōu)化。而要讓編譯器有優(yōu)化的余地,則除數(shù)必須為常數(shù),而這也可以用const修飾一個(gè)變量來達(dá)到目的。
九、將小粒度函數(shù)聲明為內(nèi)聯(lián)函數(shù)(inline) 正如我們所知,調(diào)用函數(shù)是需要保護(hù)現(xiàn)場,為局部變量分配內(nèi)存,函數(shù)結(jié)束后還要恢復(fù)現(xiàn)場等開銷,而內(nèi)聯(lián)函數(shù)則是把它的代碼直接寫到調(diào)用函數(shù)處,所以不需要這些開銷,但會(huì)使程序的源代碼長度變大。 所以若是小粒度的函數(shù),如下面的Max函數(shù),由于不需要調(diào)用普通函數(shù)的開銷,所以可以提高程序的效率。
int Max(int a, int b){ return a>b?a:b;}十、多用直接初始化 與直接初始化對應(yīng)的是復(fù)制初始化,什么是直接初始化?什么又是復(fù)制初始化?舉個(gè)簡單的例子,
ClassTest ct1;ClassTest ct2(ct1); //直接初始化ClassTest ct3 = ct1; //復(fù)制初始化那么直接初始化與復(fù)制初始化又有什么不同呢?直接初始化是直接以一個(gè)對象來構(gòu)造另一個(gè)對象,如用ct1來構(gòu)造ct2,復(fù)制初始化是先構(gòu)造一個(gè)對象,再把另一個(gè)對象值復(fù)制給這個(gè)對象,如先構(gòu)造一個(gè)對象ct3,再把ct1中的成員變量的值復(fù)制給ct3,從這里,可以看出直接初始化的效率更高一點(diǎn),而且使用直接初始化還是一個(gè)好處,就是對于不能進(jìn)行復(fù)制操作的對象,如流對象,是不能使用賦值初始化的,只能進(jìn)行直接初始化。可能我說得不太清楚,那么下面就引用一下經(jīng)典吧! 以下是PRimer是的原話: “當(dāng)用于類類型對象時(shí),初始化的復(fù)制形式和直接形式有所不同:直接初始化直接調(diào)用與實(shí)參匹配的構(gòu)造函數(shù),復(fù)制初始化總是調(diào)用復(fù)制構(gòu)造函數(shù)。復(fù)制初始化首先使用指定構(gòu)造函數(shù)創(chuàng)建一個(gè)臨時(shí)對象,然后用復(fù)制構(gòu)造函數(shù)將那個(gè)臨時(shí)對象復(fù)制到正在創(chuàng)建的對象”,還有一段這樣說,“通常直接初始化和復(fù)制初始化僅在低級別優(yōu)化上存在差異,然而,對于不支持復(fù)制的類型,或者使用非explicit構(gòu)造函數(shù)的時(shí)候,它們有本質(zhì)區(qū)別:
ifstream file1("filename")://ok:direct initializationifstream file2 = "filename";//error:copy constructor is private注:如還對直接初始化和復(fù)制初始化有疑問,可以參考一下前面的一篇文章: C++直接初始化與復(fù)制初始化的區(qū)別深入解析,里面有有關(guān)直接初始化和復(fù)制初始化的詳細(xì)解釋。 補(bǔ)充: 這里只是一點(diǎn)點(diǎn)的建議,雖然說了這么多,但是還是要說一下的就是:要避免不必要的優(yōu)化,避免不成熟的優(yōu)化,不成熟的優(yōu)化的是錯(cuò)誤的來源,因?yàn)榫幾g器會(huì)為你做很多你所不知道的優(yōu)化。 希望本文所述對提高大家C++程序設(shè)計(jì)效率能有所幫助。
轉(zhuǎn)載自:http://www.jb51.net/article/54792.htm
十一、推遲定義本地變量 雖然C語言中標(biāo)準(zhǔn)是將變量統(tǒng)一定義在開頭,但是在C++中最好放棄這種做法,因?yàn)檫@會(huì)帶來不必要的開銷,而且費(fèi)時(shí)費(fèi)力。 定義一個(gè)對象變量通常需要調(diào)用一次函數(shù)(構(gòu)造函數(shù))。如果一個(gè)變量只在某些情況下需要(例如在一個(gè)if聲明語句內(nèi)),僅在其需要的時(shí)候定義,這樣,構(gòu)造函數(shù)僅在其被使用的時(shí)候調(diào)用。 并且,推遲變量的定義會(huì)提高程序的效率,增強(qiáng)程序的可讀性,形成更好的可視性。
十二、在一大段內(nèi)存進(jìn)行初始化時(shí),盡量使用memset,例如數(shù)組的多次初始化
十三、如果程序中cout使用次數(shù)很少或只用一次,則可以使用std::cout來節(jié)省空間。因?yàn)檫@樣比導(dǎo)入整個(gè)命名空間更經(jīng)濟(jì)
十四、對于類的對象返回引用比返回對象的效率要高。 因?yàn)椴粫?huì)調(diào)用拷貝構(gòu)造函數(shù),生成臨時(shí)對象;但是特別注意臨時(shí)對象和局部變量不能返回引用;
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注