C 程式加速的12個方法

2021-09-01 12:37:26 字數 4573 閱讀 8320

需要重複使用的資料,比如載入的,csv檔案等資料,存放在全域性變數裡面,每次載入dll時,只載入一次,直到解除安裝dll,這些資料一直保持在記憶體中,避免重複載入,經過測試,這樣處理之後,漏裝檢測的時間由2.5s降低到了1.5s,因為反覆讀取檔案,是乙個很消耗時間的操作,要盡量避免。

全域性變數的使用參考文獻:

如果程式中有可以同時進行的**,譬如五道算術題,大家每人算一道,最後只花費1/5的時間就解決了5道題,多執行緒就是這個思路,現代的cpu都是多核的,就相當於可以同時算五道題目,一旦你的**使用了多執行緒,恐怕你再也回不到單執行緒的時代了。

有一段程式花費300ms,我們開了32個多執行緒之後花費16ms,加速了20倍,更別提gpu動輒百倍的加速效果。

多執行緒參考文獻:

通常使用自加、自減指令和復合賦值表示式(如a-=1及a+=1等)都能夠生成高質量的程式**,編譯器通常都能夠生成inc和dec之類的指令,而使用a=a+1或a=a-1之類的指令,有很多c編譯器都會生成二到三個位元組的指令。

無論是整數還是浮點數的運算,除法都會比較耗時,所以最後將除法運算等效成乘法運算。例如:a/b>20可改為a>b*20,可以簡化程式的執行時間。

至於其中的原因,相信大家也很清楚,如果引數是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修飾符,引用是不能引用常量的。

舉個例子,我們使用值傳遞和指標傳遞的方式傳遞一張10m的,使用值傳遞會多花費0.1s的時間。假如我們使用了10次值傳遞,將會增加1s的執行時間。

請看下面的兩段**:

**1:

classtest ct;

for(inti = 0; i < 100; ++i)

**2:

for(inti = 0; i < 100; ++i)

你會覺得哪段**的執行效率較高呢?**1科學家是**2?其實這種情況下,哪段**的效率更高是不確定的,或者說是由這個類classtest本向決定的,分析如下:

對於**1:需要呼叫classtest的建構函式1次,賦值操作函式(operator=)100次;對於**2:需要高用(複製)建構函式100次,析構函式100次。

如果呼叫賦值操作函式的開銷比呼叫建構函式和析構函式的總開銷小,則第一種效率高,否則第二種的效率高。

大部分情況下我們建議迴圈外定義,也就是方法一

現在請看下面的兩段**,

**1:

for(inti = 0; i < n; ++i)

**2:

for(inti = 0; i < n; ++i)

for(inti = 0; i < n; ++i)

注:這裡的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次即可。

很多人認為區域性變數在使用到時才會在記憶體中分配,也就是儲存單元,而靜態變數在程式的一開始便存在於記憶體中,所以使用靜態變數的效率應該比區域性變數高,其實這是乙個誤區,使用區域性變數的效率比使用靜態變數要高。

這是因為區域性變數是存在於堆疊中的,對其空間的分配僅僅是修改一次esp暫存器的內容即可(即使定義一組區域性變數也是修改一次)。而區域性變數存在於堆疊中最大的好處是,函式能重複使用記憶體,當乙個函式呼叫完畢時,退出程式堆疊,記憶體空間被**,當新的函式被呼叫時,區域性變數又可以重新使用相同的位址。當一塊資料被反覆讀寫,其資料會留在cpu的一級快取(cache)中,訪問速度非常快。而靜態變數卻不存在於堆疊中。

可以說靜態變數是低效的

在c++中,支援多繼承,即乙個子類可以有多個父類。書上都會跟我們說,多重繼承的複雜性和使用的困難,並告誡我們不要輕易使用多重繼承。其實多重繼承並不僅僅使程式和**變得更加複雜,還會影響程式的執行效率。

這是因為在c++中每個物件都有乙個this指標指向物件本身,而c++中類對成員變數的使用是通過this的位址加偏移量來計算的,而在多重繼承的情況下,這個計算會變數更加複雜,從而降低程式的執行效率。而為了解決二義性,而使用虛基類的多重繼承對效率的影響更為嚴重,因為其繼承關係更加複雜和成員變數所屬的父類關係更加複雜。

正如我們所知,呼叫函式是需要保護現場,為區域性變數分配記憶體,函式結束後還要恢復現場等開銷,而內聯函式則是把它的**直接寫到呼叫函式處,所以不需要這些開銷,但會使程式的源**長度變大。

所以若是小粒度的函式,如下面的max函式,由於不需要呼叫普通函式的開銷,所以可以提高程式的效率。

inline max(inta, intb)

什麼是內聯函式

與直接初始化對應的是複製初始化,什麼是直接初始化?什麼又是複製初始化?舉個簡單的例子,

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

dynamic_cast的作用是進行指標或引用的型別轉換,dynamic_cast的轉換需要目標型別和源物件有一定的關係:繼承關係。 實現從子類到基類的指標轉換,實際上這種轉換是非常低效的,對程式的效能影響也比較大,不可大量使用,而且繼承關係越複雜,層次越深,其轉換時間開銷越大。在程式中應該儘量減少使用。

加速Linux程式編譯的幾種方法

專案越來越大,每次需要重新編譯整個專案都是一件很浪費時間的事情。research了一下,找到以下可以幫助提高速度的方法,總結一下。有人說在windows下用了ramdisk把乙個專案編譯時間從4.5小時減少到了5分鐘,也許這個數字是有點誇張了,不過粗想想,把檔案放到記憶體上做編譯應該是比在磁碟上快多...

C 加速輸入的幾種方法

在c 中,cin和cout的速度其實不並不慢,c 中的流的io速度相當的快,其速度與初始設定的快取區大小和硬碟的io速度有關。但在c 中,為了相容c的io scanf和printf cin和cout被設定為與c的io同步,這樣導致cin和cout的速度不如scanf和printf快。另外,在預設情況...

1 2最簡單的c 程式

include using namespace std int main 先看看程式中,第三行,其中main代表 主函式 的名字。每乙個c 程式都必須有乙個main函式。main前面的int作用是宣告函式的型別為整型 標準的c 要求main函式必須宣告為int型。有的作業系統 要求執行程式後必須向作...