C 效能優化實踐

2021-07-26 10:32:48 字數 3648 閱讀 9859

優化準則:

1.二八法則:在任何一組東西中,最重要的只佔其中一小部分,約20%

,其餘80%

的儘管是多數,卻是次要的;在優化實踐中,我們將精力集中在優化那20%

最耗時的**上,整體效能將有顯著的提公升;這個很好理解。函式a

雖然**量大,但在一次正常執行流程中,只呼叫了一次。而另乙個函式b

**量比a

小很多,但被呼叫了1000

次。顯然,我們更應關注b

的優化。

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

工具:1gprof

工欲善其事,必先利其器。對於linux

平台下c++

的優化,我們使用gprof

工具。gprof

是gnuprofile

工具,可以執行於linux

、aix

、sun

等作業系統進行c

、c++

、pascal

、fortran

程式的效能分析,用於程式的效能優化以及程式瓶頸問題的查詢和解決。通過分析應用程式執行時產生的「flatprofile」

,可以得到每個函式的呼叫次數,消耗的cpu

時間(只統計cpu

時間,對io

瓶頸無能為力),也可以得到函式的「呼叫關係圖」,包括函式呼叫的層次關係,每個函式呼叫花費了多少時間。

2.gprof

使用步驟

1)   

用gcc

、g++

、xlc

編譯程式時,使用-pg

引數,如:g++-pg -o test.exetest.cpp

編譯器會自動在目標**中插入用於效能測試的**片斷,這些**在程式執行時採集並記錄函式的呼叫關係和呼叫次數,並記錄函式自身執行時間和被呼叫函式的執行時間。

2)   

執行編譯後的可執行程式,如:./test.exe

。該步驟執行程式的時間會稍慢於正常編譯的可執行程式的執行時間。程式執行結束後,會在程式所在路徑下生成乙個預設檔名為gmon.out

的檔案,這個檔案就是記錄程式執行的效能、呼叫關係、呼叫次數等資訊的資料檔案。

3)   

使用gprof

命令來分析記錄程式執行資訊的gmon.out

檔案,如:gproftest.exe gmon.out

則可以在顯示器上看到函式呼叫相關的統計、分析資訊。上述資訊也可以採用gproftest.exe gmon.out>gprofresult.txt

重定向到文字檔案以便於後續分析。

以上只是gpro

的使用步驟簡介,關於gprof

使用例項詳見附錄1;實踐

我們的程式遇到了效能瓶頸,在採用架構改造,改用記憶體資料庫之前,我們考慮從**級入手,先嘗試**級的優化;通過使用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

的直接操作。這樣的包裝,帶來的好處就是不用直接與底層資料庫互動,在**編寫方面方便不少,**可讀性也很好;帶來的問題就是效能的損失;

分析:(2

點原因)

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)

改造後的邏輯:

string recordset::getfield(const string &strname)

_singlelist::iterator _singlelist::iterator::operator++(int)//後加

從後加的實現方式可以知道,物件利用自己建立乙個臨時物件(自己在函式呼叫的乙個複製),然後改變自己的狀態,並返回這個臨時物件,而前加的實現方式時,直接改變自己的內部狀態,並返回自己的引用。

從第一點的論述可以知道後加實現時會呼叫複製建構函式,在函式返回時還要呼叫析構函式,而由於前加實現方式直接改變物件的內部狀態,並返回自己的引用,至始至終也沒有建立新的物件,所以也就不會呼叫建構函式和析構函式。

然而更加糟糕的是,迭代器通常是用來遍歷容器的,它大多應用在迴圈中,試想你的鍊錶有100個元素,用下面的兩種方式遍歷:?

12

3

4

5

6

7

8

9

for(_singlelist::iterator it = list.begin(); it != list.end(); ++it)

for(_singlelist::iterator it = list.begin(); it != list.end(); it++)

如果你的習慣不好,寫了第二種形式,那麼很不幸,做同樣的事情,就是因為乙個前加和乙個後加的區別,你就要呼叫多200個函式,其對效率的影響可就不可忽視了。

參考:

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...