C 效能優化實踐

2021-08-22 04:30:49 字數 2765 閱讀 2863

一、優化準則

1. 二八法則:在任何一組東西中,最重要的只佔其中一小部分,約20%,其餘80%的儘管是多數,卻是次要的;在優化實踐中,我們將精力集中在優化那20%最耗時的**上,整體效能將有顯著的提公升;這個很好理解。函式a雖然**量大,但在一次正常執行流程中,只呼叫了一次。而另乙個函式b**量比a小很多,但被呼叫了1000次。顯然,我們更應關注b的優化;

2. 編完**,再優化;編碼的時候總是考慮最佳效能未必總是好的;在強調最佳效能的編碼方式的同時,可能就損失了**的可讀性和開發效率。

二、工具

1、gprof

工欲善其事,必先利其器。對於linux平台下c++的優化,我們使用gprof工具。gprof是gnu profile工具,可以執行於linux、aix、sun等作業系統進行c、c++、pascal、fortran程式的效能分析,用於程式的效能優化以及程式瓶頸問題的查詢和解決。通過分析應用程式執行時產生的「flat profile」,可以得到每個函式的呼叫次數,消耗的cpu時間(只統計cpu時間,對io瓶頸無能為力),也可以得到函式的「呼叫關係圖」,包括函式呼叫的層次關係,每個函式呼叫花費了多少時間。

2、gprof使用步驟

1. 用gcc、g++、xlc編譯程式時,使用-pg引數,如:g++ -pg -o test.exe test.cpp編譯器會自動在目標**中插入用於效能測試的**片斷,這些**在程式執行時採集並記錄函式的呼叫關係和呼叫次數,並記錄函式自身執行時間和被呼叫函式的執行時間;

2. 執行編譯後的可執行程式,如:./test.exe。該步驟執行程式的時間會稍慢於正常編譯的可執行程式的執行時間。程式執行結束後,會在程式所在路徑下生成乙個預設檔名為gmon.out的檔案,這個檔案就是記錄程式執行的效能、呼叫關係、呼叫次數等資訊的資料檔案;

3. 使用gprof命令來分析記錄程式執行資訊的gmon.out檔案,如:gprof test.exe gmon.out則可以在顯示器上看到函式呼叫相關的統計、分析資訊。上述資訊也可以採用gprof test.exe gmon.out> gprofresult.txt重定向到文字檔案以便於後續分析。

三、實踐

我們的程式遇到了效能瓶頸,在採用架構改造,改用記憶體資料庫之前,我們考慮從**級入手,先嘗試**級的優化;通過使用gprof分析,我們發現以下2個最為突出的問題:

1、初始化大物件耗時

分析報告:307 6.5% vobj1::vobj1@240038vobj1

在整個執行流程中被呼叫307次,其物件初始化耗時佔到6.5%。

這個物件很大,包含的屬性多,屬於基礎資料結構;

在程式進入建構函式函式體之前,類的父類物件和所有子成員變數物件已經被生成和構造。如果在建構函式體內位其執行賦值操作,顯示屬於浪費。如果在建構函式時已經知道如何為類的子成員變數初始化,那麼應該將這些初始化資訊通過建構函式的初始化列表賦予子成員變數,而不是在建構函式函式體中進行這些初始化。因為進入建構函式函式體之前,這些子成員變數已經初始化過一次了。

在c++程式中,建立/銷毀物件是影響效能的乙個非常突出的操作。首先,如果是從全域性堆中生成物件,則需要首先進行動態記憶體分配操作。眾所周知,動態分配/**在c/c++程式中一直都是非常耗時的。因為牽涉到尋找匹配大小的記憶體塊,找到後可能還需要截斷處理,然後還需要修改維護全域性堆記憶體使用情況資訊的鍊錶等。

解決方法:我們將大部分的初始化操作都移到初始化列表中,效能消耗降到1.8%。

2、map使用不當

分析報告:89 6.8% recordset::getfield

recordset的getfield被呼叫了89次,效能消耗佔到6.8%;

recordset是我們在在資料庫層面的包裝,對應取出資料的記錄集;(用過ado的朋友很熟悉);由於我們使用的是底層c++資料庫介面,通過對資料庫原始api進行一層包裝,從而遮蔽開發人員對底層api的直接操作。這樣的包裝,帶來的好處就是不用直接與底層資料庫互動,在**編寫方面方便不少,**可讀性也很好;帶來的問題就是效能的損失;

【分析】:

1)在getfield函式中,使用了map[「a」]來查詢資料,如果找不到「a」,則map會自動插入key 」a」,並設value為0;而m.find(「a」)不會自動插入上述pair,執行效率更高;原有邏輯:

string recordset::getfield(const

string &strname)

else

if (m_fields[strname]==0)

return m_records[nindex].getvalue(m_fields[strname] - 1) ;

}

改造後的邏輯:

string recordset::getfield(const

string &strname)

調整後的recordset::getfield的執行時間約是之前的1/2;且易讀性更高。

2)在recordset中,對於每個欄位的儲存,使用的是map m_fields; g++中的stl標準庫中預設使用的紅黑樹作為map的底層資料結構;

通過附錄中的文件2,我們發現其實有更快的結構, 在效率上,unorder map優於hash map, hash map 優於 紅黑樹;如果不要求map有序,unordered_map 是更好的選擇;

解決方法:將map結構換成unordered_map,效能消耗降到1.4%;

**:

C 效能優化實踐

優化準則 1.二八法則 在任何一組東西中,最重要的只佔其中一小部分,約20 其餘80 的儘管是多數,卻是次要的 在優化實踐中,我們將精力集中在優化那20 最耗時的 上,整體效能將有顯著的提公升 這個很好理解。函式a 雖然 量大,但在一次正常執行流程中,只呼叫了一次。而另乙個函式b 量比a 小很多,但...

C 效能優化實踐

優化準則 1.二八法則 在任何一組東西中,最重要的只佔其中一小部分,約20 其餘80 的儘管是多數,卻是次要的 在優化實踐中,我們將精力集中在優化那20 最耗時的 上,整體效能將有顯著的提公升 這個很好理解。函式a雖然 量大,但在一次正常執行流程中,只呼叫了一次。而另乙個函式b 量比a小很多,但被呼...

C 的效能優化實踐

1.二八法則 在任何一組東西中,最重要的只佔其中一小部分,約20 其餘80 的儘管是多數,卻是次要的 在優化實踐中,我們將精力集中在優化那20 最耗時的 上,整體效能將有顯著的提公升 這個很好理解。函式a雖然 量大,但在一次正常執行流程中,只呼叫了一次。而另乙個函式b 量比a小很多,但被呼叫了100...