C++性能優(yōu)化實(shí)踐

      網(wǎng)友投稿 1000 2025-03-31

      優(yōu)化準(zhǔn)則:


      1.二八法則:在任何一組東西中,最重要的只占其中一小部分,約20%,其余80%的盡管是多數(shù),卻是次要的;在優(yōu)化實(shí)踐中,我們將精力集中在優(yōu)化那20%最耗時(shí)的代碼上,整體性能將有顯著的提升;這個(gè)很好理解。函數(shù)A雖然代碼量大,但在一次正常執(zhí)行流程中,只調(diào)用了一次。而另一個(gè)函數(shù)B代碼量比A小很多,但被調(diào)用了1000次。顯然,我們更應(yīng)關(guān)注B的優(yōu)化。

      2.編完代碼,再優(yōu)化;編碼的時(shí)候總是考慮最佳性能未必總是好的;在強(qiáng)調(diào)最佳性能的編碼方式的同時(shí),可能就損失了代碼的可讀性和開發(fā)效率;

      工具:

      1Gprof

      工欲善其事,必先利其器。對(duì)于Linux平臺(tái)下C++的優(yōu)化,我們使用gprof工具。gprof是GNUprofile工具,可以運(yùn)行于linux、AIX、Sun等操作系統(tǒng)進(jìn)行C、C++、Pascal、Fortran程序的性能分析,用于程序的性能優(yōu)化以及程序瓶頸問題的查找和解決。通過分析應(yīng)用程序運(yùn)行時(shí)產(chǎn)生的“flatprofile”,可以得到每個(gè)函數(shù)的調(diào)用次數(shù),消耗的CPU時(shí)間(只統(tǒng)計(jì)CPU時(shí)間,對(duì)IO瓶頸無能為力),也可以得到函數(shù)的“調(diào)用關(guān)系圖”,包括函數(shù)調(diào)用的層次關(guān)系,每個(gè)函數(shù)調(diào)用花費(fèi)了多少時(shí)間。

      2.gprof使用步驟

      1)???用gcc、g++、xlC編譯程序時(shí),使用-pg參數(shù),如:g++-pg -o test.exetest.cpp編譯器會(huì)自動(dòng)在目標(biāo)代碼中插入用于性能測(cè)試的代碼片斷,這些代碼在程序運(yùn)行時(shí)采集并記錄函數(shù)的調(diào)用關(guān)系和調(diào)用次數(shù),并記錄函數(shù)自身執(zhí)行時(shí)間和被調(diào)用函數(shù)的執(zhí)行時(shí)間。

      2)???執(zhí)行編譯后的可執(zhí)行程序,如:./test.exe。該步驟運(yùn)行程序的時(shí)間會(huì)稍慢于正常編譯的可執(zhí)行程序的運(yùn)行時(shí)間。程序運(yùn)行結(jié)束后,會(huì)在程序所在路徑下生成一個(gè)缺省文件名為gmon.out的文件,這個(gè)文件就是記錄程序運(yùn)行的性能、調(diào)用關(guān)系、調(diào)用次數(shù)等信息的數(shù)據(jù)文件。

      3)???使用gprof命令來分析記錄程序運(yùn)行信息的gmon.out文件,如:gproftest.exe gmon.out則可以在顯示器上看到函數(shù)調(diào)用相關(guān)的統(tǒng)計(jì)、分析信息。上述信息也可以采用gproftest.exe gmon.out>gprofresult.txt重定向到文本文件以便于后續(xù)分析。

      以上只是gpro的使用步驟簡(jiǎn)介,關(guān)于gprof使用實(shí)例詳見附錄1;

      實(shí)踐

      我們的程序遇到了性能瓶頸,在采用架構(gòu)改造,改用內(nèi)存數(shù)據(jù)庫之前,我們考慮從代碼級(jí)入手,先嘗試代碼級(jí)的優(yōu)化;通過使用gprof分析,我們發(fā)現(xiàn)以下2個(gè)最為突出的問題:

      1.初始化大對(duì)象耗時(shí)

      分析報(bào)告:307??6.5%VOBJ1::VOBJ1@240038VOBJ1

      在整個(gè)執(zhí)行流程中被調(diào)用307次,其對(duì)象初始化耗時(shí)占到6.5%。

      這個(gè)對(duì)象很大,包含的屬性多,屬于基礎(chǔ)數(shù)據(jù)結(jié)構(gòu);

      在程序進(jìn)入構(gòu)造函數(shù)函數(shù)體之前,類的父類對(duì)象和所有子成員變量對(duì)象已經(jīng)被生成和構(gòu)造。如果在構(gòu)造函數(shù)體內(nèi)位其執(zhí)行賦值操作,顯示屬于浪費(fèi)。如果在構(gòu)造函數(shù)時(shí)已經(jīng)知道如何為類的子成員變量初始化,那么應(yīng)該將這些初始化信息通過構(gòu)造函數(shù)的初始化列表賦予子成員變量,而不是在構(gòu)造函數(shù)函數(shù)體中進(jìn)行這些初始化。因?yàn)檫M(jìn)入構(gòu)造函數(shù)函數(shù)體之前,這些子成員變量已經(jīng)初始化過一次了。

      在C++程序中,創(chuàng)建/銷毀對(duì)象是影響性能的一個(gè)非常突出的操作。首先,如果是從全局堆中生成對(duì)象,則需要首先進(jìn)行動(dòng)態(tài)內(nèi)存分配操作。眾所周知,動(dòng)態(tài)分配/回收在C/C++程序中一直都是非常耗時(shí)的。因?yàn)闋可娴綄ふ移ヅ浯笮〉膬?nèi)存塊,找到后可能還需要截?cái)嗵幚恚缓筮€需要修改維護(hù)全局堆內(nèi)存使用情況信息的鏈表等。

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

      2.Map使用不當(dāng)

      分析報(bào)告:89??6.8%Recordset::GetField

      Recordset的getField被調(diào)用了89次,性能消耗占到6.8%;

      Recordset是我們?cè)谠跀?shù)據(jù)庫層面的包裝,對(duì)應(yīng)取出數(shù)據(jù)的記錄集;(用過ADO的朋友很熟悉);由于我們使用的是底層c++數(shù)據(jù)庫接口,通過對(duì)數(shù)據(jù)庫原始api進(jìn)行一層包裝,從而屏蔽開發(fā)人員對(duì)底層api的直接操作。這樣的包裝,帶來的好處就是不用直接與底層數(shù)據(jù)庫交互,在代碼編寫方面方便不少,代碼可讀性也很好;帶來的問題就是性能的損失;

      分析:(2點(diǎn)原因)

      1)在GetField函數(shù)中,使用了map[“a”]來查詢數(shù)據(jù),如果找不到“a”,則map會(huì)自動(dòng)插入key”a”,并設(shè)value為0;而m.find(“a”)不會(huì)自動(dòng)插入上述pair,執(zhí)行效率更高;原有邏輯:

      string Recordset::GetField(const string &strName)

      {

      int nIndex;

      if (hasIndex==false)

      {

      nIndex =m_nPos;

      }

      else

      {

      nIndex =m_vSort[m_nPos].m_iorder;

      }

      if (m_fields[strName]==0)

      {

      LOG_ERR("Recordset::GetField:"<

      return m_records[nIndex].GetValue(m_fields[strName] - 1);

      }

      改造后的邏輯:

      string Recordset::GetField(const string &strName)

      {

      unordered_map::iterator iter = m_fields.find(strName);

      if (iter == m_fields.end())

      {

      LOG_ERR("[Recordset::GetField] "<< strName

      調(diào)整后的Recordset::GetField的執(zhí)行時(shí)間約是之前的1/2;且易讀性更高;

      2)在Recordset中,對(duì)于每個(gè)字段的存儲(chǔ),使用的是mapm_fields;?g++中的stl標(biāo)準(zhǔn)庫中默認(rèn)使用的紅黑樹作為map的底層數(shù)據(jù)結(jié)構(gòu);

      通過附錄中的文檔2,我們發(fā)現(xiàn)其實(shí)有更快的結(jié)構(gòu),在效率上,unordermap優(yōu)于hashmap, hash map 優(yōu)于紅黑樹;如果不要求map有序,unordered_map是更好的選擇;

      解決方法:將map結(jié)構(gòu)換成unordered_map,性能消耗降到1.4%;

      總結(jié)

      我們修改不到30行代碼,整體性能提升10%左右,效果明顯;打蛇打七寸,性能優(yōu)化的關(guān)鍵在于找準(zhǔn)待優(yōu)化的點(diǎn),之后的事,也就水到渠成;

      bythe way,對(duì)于Linux平臺(tái)使用C++工作的朋友,推薦一本好書:《程序員的自我修養(yǎng)》。這本書介紹了運(yùn)行庫相關(guān)的各種技術(shù)。對(duì)裝載、鏈接和庫進(jìn)行了深入淺出的剖析。看過真是大呼過癮;

      附錄:

      附1:prof工具介紹及實(shí)踐

      附2:maphash_map unordered_map性能測(cè)試

      二、++i和i++引申出的效率問題

      看了上面的第一點(diǎn),你可能覺得,那不就是多調(diào)用了四個(gè)函數(shù)而已,你可能對(duì)此不屑一顧。那么來看看下面的例子,應(yīng)該會(huì)讓你大吃一驚。

      至于整型變量的前加和后加的區(qū)別相信大家也是很清楚的。然而在這里我想跟大家談的卻是C++類的運(yùn)算符重載,為了與整形變量的用法一致,在C++中重載運(yùn)算符++時(shí)一般都會(huì)把前加和后加都重載。你可能會(huì)說,你在代碼中不會(huì)重載++運(yùn)算符,但是你敢說你沒有使用過類的++運(yùn)算符重載嗎?迭代器類你總使用過吧!可能到現(xiàn)在你還不是很懂我在說什么,那么就先看看下面的例子吧,是本人為鏈表寫的一個(gè)內(nèi)部迭代器。

      ?

      1

      2

      3

      4

      5

      6

      7

      8

      9

      C++性能優(yōu)化實(shí)踐

      10

      11

      _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)方式可以知道,對(duì)象利用自己創(chuàng)建一個(gè)臨時(shí)對(duì)象(自己在函數(shù)調(diào)用的一個(gè)復(fù)制),然后改變自己的狀態(tài),并返回這個(gè)臨時(shí)對(duì)象,而前加的實(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)方式直接改變對(duì)象的內(nèi)部狀態(tài),并返回自己的引用,至始至終也沒有創(chuàng)建新的對(duì)象,所以也就不會(huì)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)

      然而更加糟糕的是,迭代器通常是用來遍歷容器的,它大多應(yīng)用在循環(huán)中,試想你的鏈表有100個(gè)元素,用下面的兩種方式遍歷:

      ?

      1

      2

      3

      4

      5

      6

      7

      8

      9

      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ù),其對(duì)效率的影響可就不可忽視了。

      參考:http://blog.chinaunix.net/uid-29068482-id-3870978.html

      C++

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:制作好的快閃PPTX怎么在PPT里轉(zhuǎn)化為視頻發(fā)布(如何制作快閃ppt 教程)
      下一篇:項(xiàng)目組織管理方式,打造成功的項(xiàng)目之道
      相關(guān)文章
      亚洲av色香蕉一区二区三区蜜桃| 亚洲毛片基地4455ww| 久久亚洲AV成人无码| 亚洲精品无码久久久久去q | 亚洲a级片在线观看| 亚洲一区二区三区高清| 亚洲精品偷拍视频免费观看| 亚洲AV无码XXX麻豆艾秋| 亚洲成a人片在线观| 亚洲色偷偷偷网站色偷一区| 亚洲avav天堂av在线不卡| 亚洲av无码乱码国产精品| 亚洲成AV人片在线观看ww| 亚洲男人的天堂www| 亚洲日韩乱码中文无码蜜桃臀网站| 亚洲色欲久久久综合网| 亚洲精品国自产拍在线观看| 狼人大香伊蕉国产WWW亚洲| 国产AV无码专区亚洲AV琪琪 | 亚洲综合色丁香婷婷六月图片| 亚洲成a人片在线观看精品| 亚洲国产精品热久久| 亚洲成AV人片在线观看ww| 久久久久亚洲AV无码网站| 亚洲视频手机在线| 亚洲免费中文字幕| 亚洲色欲啪啪久久WWW综合网| 亚洲heyzo专区无码综合| 337P日本欧洲亚洲大胆精品| 亚洲国产精品无码久久青草| 亚洲综合精品网站在线观看| 美腿丝袜亚洲综合| 亚洲AV永久无码精品一百度影院| 亚洲午夜久久久精品影院| 亚洲欧洲国产成人精品| 亚洲中文无码亚洲人成影院| 老牛精品亚洲成av人片| 久久久久亚洲AV无码专区网站| 国产成人无码综合亚洲日韩 | 另类图片亚洲校园小说区| 国产亚洲精品线观看动态图|