程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 進步C++法式運轉效力的10個簡略辦法

進步C++法式運轉效力的10個簡略辦法

編輯:關於C++

進步C++法式運轉效力的10個簡略辦法。本站提示廣大學習愛好者:(進步C++法式運轉效力的10個簡略辦法)文章只能為提供參考,不一定能成為您想要的結果。以下是進步C++法式運轉效力的10個簡略辦法正文


本文以C/C++法式為例講述了法式運轉效力的10個簡略辦法,分享給年夜家供年夜家參考之用。詳細剖析以下:

關於每個法式員來講,法式的運轉效力都是一個值得看重,並為之支付盡力的成績。然則法式機能的優化也是一門龐雜的學問,須要許多的常識,但是其實不是每一個法式員都具有如許的常識,並且闡述若何優化法式進步法式運轉效力的書本也很少。然則這其實不等於我們可以疏忽法式的運轉效力,上面就引見一下自己積聚的一些簡略適用的進步法式運轉效力的辦法,願望對年夜家有所贊助。

1、盡可能削減值傳遞,多用援用來傳遞參數。
至於個中的緣由,信任年夜家也很清晰,假如參數是int等說話自界說的類型能夠能機能的影響還不是很年夜,然則假如參數是一個類的對象,那末其效力成績就不問可知了。例如一個斷定兩個字符串能否相等的函數,其聲明以下:

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)

個中若應用第一個函數(值傳遞),則在參數傳遞和函數前往時,須要挪用string的結構函數和析構函數兩次(即共多挪用了四個函數),而其他的三個函數(指針傳遞和援用傳遞)則不須要挪用這四個函數。由於指針和援用都不會創立新的對象。假如一個結構一個對象和析構一個對象的開支是宏大的,這就是會效力形成必定的影響。

但是在許多人的眼中,指針是一個噩夢,應用指針就意味著毛病,那末就應用援用吧!它與應用通俗值傳遞一樣便利直不雅,同時具有指針傳遞的高效和才能。由於援用是一個變量的別號,對其操作同等於對現實對象操作,所以當你肯定在你的函數是不會或不須要變量參數的值時,就年夜膽地在聲明的後面加上一個const吧,就如最初的一個函數聲明一樣。

同時加上一個const還有一個利益,就是可以對常量停止援用,若不加上const潤飾符,援用是不克不及援用常量的。

2、++i和i++引伸出的效力成績

看了下面的第一點,你能夠認為,那不就是多挪用了四個函數罷了,你能夠對此嗤之以鼻。那末來看看上面的例子,應當會讓你年夜吃一驚。

至於整型變量的前加和後加的差別信任年夜家也是很清晰的。但是在這裡我想跟年夜家談的倒是C++類的運算符重載,為了與整形變量的用法分歧,在C++中重載運算符++時普通都邑把前加和後加都重載。你能夠會說,你在代碼中不會重載++運算符,然則你敢說你沒有應用過類的++運算符重載嗎?迭代器類你總應用過吧!能夠到如今你還不是很懂我在說甚麼,那末就先看看上面的例子吧,是自己為鏈表寫的一個外部迭代器。

_SingleList::Iterator& _SingleList::Iterator::operator++()//前加
{
  pNote = pNote->pNext;
  return *this;
}
_SingleList::Iterator _SingleList::Iterator::operator++(int)//後加
{
  Iterator tmp(*this);
  pNote = pNote->pNext;
  return tmp;
}

從後加的完成方法可以曉得,對象應用本身創立一個暫時對象(本身在函數挪用的一個復制),然後轉變本身的狀況,並前往這個暫時對象,而前加的完成方法時,直接轉變本身的外部狀況,並前往本身的援用。

從第一點的闡述可以曉得後加完成時會挪用復制結構函數,在函數前往時還要挪用析構函數,而因為前加完成方法直接轉變對象的外部狀況,並前往本身的援用,至始至終也沒有創立新的對象,所以也就不會挪用結構函數和析構函數。

但是加倍蹩腳的是,迭代器平日是用來遍歷容器的,它年夜多運用在輪回中,試想你的鏈表有100個元素,用上面的兩種方法遍歷:

for(_SingleList::Iterator it = list.begin(); it != list.end(); ++it)
{
  //do something
} 

for(_SingleList::Iterator it = list.begin(); it != list.end(); it++)
{
  //do something
} 

假如你的習氣欠好,寫了第二種情勢,那末很不幸,做異樣的工作,就是由於一個前加和一個後加的差別,你就要挪用多200個函數,其對效力的影響可就弗成疏忽了。

3、輪回激發的評論辯論1(輪回內界說,照樣輪回外界說對象)

請看上面的兩段代碼:

代碼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
}

你會認為哪段代碼的運轉效力較高呢?代碼1迷信家是代碼2?其實這類情形下,哪段代碼的效力更高是不肯定的,或許說是由這個類ClassTest本向決議的,剖析以下:

關於代碼1:須要挪用ClassTest的結構函數1次,賦值操作函數(operator=)100次;關於代碼2:須要高用(復制)結構函數100次,析構函數100次。

假如挪用賦值操作函數的開支比挪用結構函數和析構函數的總開支小,則第一種效力高,不然第二種的效力高。

4、輪回激發的評論辯論2(防止過年夜的輪回)

如今請看上面的兩段代碼,
代碼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()是沒有聯系關系的,即兩段代碼所發生的成果是一樣的。

以代碼的層面下去看,仿佛是代碼1的效力更高,由於究竟代碼1少了n次的自加運算和斷定,究竟自加運算和斷定也是須要時光的。然則實際真的是如許嗎?

這就要看fun1和fun2這兩個函數的范圍(或龐雜性)了,假如這多個函數的代碼語句很少,則代碼1的運轉效力高一些,然則若fun1和fun2的語句有許多,范圍較年夜,則代碼2的運轉效力會比代碼1明顯高很多。能夠你不明確這是為何,要說是為何這要由盤算機的硬件說起。

因為CPU只能從內存在讀取數據,而CPU的運算速度遠弘遠於內存,所認為了進步法式的運轉速度有用天時用CPU的才能,在內存與CPU之間有一個叫Cache的存儲器,它的速度接近CPU。而Cache中的數據是從內存中加載而來的,這個進程須要拜訪內存,速度較慢。

這裡先說說Cache的設計道理,就是時光部分性和空間部分性。時光部分性是指假如一個存儲單位被拜訪,則能夠該單位會很快被再次拜訪,這是由於法式存在著輪回。空間部分性是指假如一個貯存單位被拜訪,則該單位臨近的單位也能夠很快被拜訪,這是由於法式中年夜部門指令是次序存儲、次序履行的,數據也普通也是以向量、數組、樹、表等情勢簇聚在一路的。

看到這裡你能夠曾經明確個中的緣由了。沒錯,就是如許!假如fun1和fun2的代碼量很年夜,例如都年夜於Cache的容量,則在代碼1中,就不克不及充足應用Cache了(由時光部分性和空間部分性可知),由於每輪回一次,都要把Cache中的內容踢出,從新從內存中加載另外一個函數的代碼指令和數據,而代碼2則更很好天時用了Cache,應用兩個輪回語句,每一個輪回所用到的數據簡直都已加載到Cache中,每次輪回都可從Cache中讀寫數據,拜訪內存較少,速度較快,實際下去說只須要完整踢出fun1的數據1次便可。

5、部分變量VS靜態變量

許多人以為部分變量在應用到時才會在內存平分配貯存單位,而靜態變量在法式的一開端便存在於內存中,所以應用靜態變量的效力應當比部分變量高,其實這是一個誤區,應用部分變量的效力比應用靜態變量要高。

這是由於部分變量是存在於客棧中的,對其空間的分派僅僅是修正一次esp存放器的內容便可(即便界說一組部分變量也是修正一次)。而部分變量存在於客棧中最年夜的利益是,函數能反復應用內存,當一個函數挪用終了時,加入法式客棧,內存空間被收受接管,當新的函數被挪用時,部分變量又可以從新應用雷同的地址。當一塊數據被重復讀寫,其數據會留在CPU的一級緩存(Cache)中,拜訪速度異常快。而靜態變量卻不存在於客棧中。

可以說靜態變量是低效的。

6、防止應用多重繼續

在C++中,支撐多繼續,即一個子類可以有多個父類。書上都邑跟我們說,多重繼續的龐雜性和應用的艱苦,並申饬我們不要隨意馬虎應用多重繼續。其實多重繼續其實不僅僅使法式和代碼變得加倍龐雜,還會影響法式的運轉效力。

這是由於在C++中每一個對象都有一個this指針指向對象自己,而C++中類對成員變量的應用是經由過程this的地址加偏移量來盤算的,而在多重繼續的情形下,這個盤算會變量加倍龐雜,從而下降法式的運轉效力。而為懂得決二義性,而應用虛基類的多重繼續對效力的影響更加嚴重,由於其繼續關系加倍龐雜和成員變量所屬的父類關系加倍龐雜。

7、盡可能少應用dynamic_cast

dynamic_cast的感化是停止指針或援用的類型轉換,dynamic_cast的轉換須要目的類型和源對象有必定的關系:繼續關系。 完成從子類到基類的指針轉換,現實上這類轉換長短常低效的,對法式的機能影響也比擬年夜,弗成年夜量應用,並且繼續關系越龐雜,條理越深,其轉換時光開支越年夜。在法式中應當盡可能削減應用。

8、削減除法運算的應用

不管是整數照樣浮點數運算,除法都是一件運算速度很慢的指令,在盤算機中完成除法是比擬龐雜的。所以要削減除法運算的次數,上面引見一些簡略辦法來進步效力:
1、經由過程數學的辦法,把除法變成乘法運算,如if(a > b/c),假如a、b、c都是負數,則可寫成if(a*c > b)
2、讓編譯器有優化的余地,如裡你要做的運算是int型的n/8的話,寫成(unsigned)n/8有益於編譯器的優化。而要讓編譯器有優化的余地,則除數必需為常數,而這也能夠用const潤飾一個變量來到達目標。

9、將小粒度函數聲明為內聯函數(inline)

正如我們所知,挪用函數是須要掩護現場,為部分變量分派內存,函數停止後還要恢復現場等開支,而內聯函數則是把它的代碼直接寫到挪用函數處,所以不須要這些開支,但會使法式的源代碼長度變年夜。

所以若是小粒度的函數,以下面的Max函數,因為不須要挪用通俗函數的開支,所以可以進步法式的效力。

int Max(int a, int b)
{
  return a>b?a:b;
}

10、多用直接初始化

與直接初始化對應的是復制初始化,甚麼是直接初始化?甚麼又是復制初始化?舉個簡略的例子,

ClassTest ct1;
ClassTest ct2(ct1);  //直接初始化
ClassTest ct3 = ct1;  //復制初始化

那末直接初始化與復制初始化又有甚麼分歧呢?直接初始化是直接以一個對象來結構另外一個對象,如用ct1來結構ct2,復制初始化是先結構一個對象,再把另外一個對象值復制給這個對象,如先結構一個對象ct3,再把ct1中的成員變量的值復制給ct3,從這裡,可以看出直接初始化的效力更高一點,並且應用直接初始化照樣一個利益,就是關於不克不及停止復制操作的對象,如流對象,是不克不及應用賦值初始化的,只能停止直接初始化。能夠我說得不太清晰,那末上面就援用一下經典吧!

以下是Primer是的原話:

“當用於類類型對象時,初始化的復制情勢和直接情勢有所分歧:直接初始化直接挪用與實參婚配的結構函數,復制初始化老是挪用復制結構函數。復制初始化起首應用指定結構函數創立一個暫時對象,然後用復制結構函數將誰人暫時對象復制到正在創立的對象”,還有一段如許說,“平日直接初始化和復制初始化僅在初級別優化上存在差別,但是,關於不支撐復制的類型,或許應用非explicit結構函數的時刻,它們有實質差別:
ifstream file1("filename")://ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private

注:如還對直接初始化和復制初始化有疑問,可以參考一下後面的一篇文章:
C++直接初始化與復制初始化的差別深刻解析,外面有有關直接初始化和復制初始化的具體說明。

彌補:

這裡只是一點點的建議,固然說了這麼多,然則照樣要說一下的就是:要防止不用要的優化,防止不成熟的優化,不成熟的優化的是毛病的起源,由於編譯器會為你做許多你所不曉得的優化。

願望本文所述對進步年夜家C++法式設計效力能有所贊助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved