1.
for(int i = 0 ; i<s.size(); ++i)
for(int i = 0, len = s.size(); i<len; ++i)
第二種避免多次調用size()函數,雖然第一種c++編譯時會自動編譯成內聯函數,但還是第二種好
2.定義變量在循環外面比里面好
在里面定義每次都會重新定義,如果是類的話,每次還都會調用構造函數和析構函數。建議大一點的對象都在類外定義,小的在里面定義(增加可讀性)
如果賦值的話就要看邏輯了,這里說的是定義
3.
除法比乘法更慢
4.
內聯inline 用於函數短小,反復調用情況
5.
const不僅可以保證變量不被修改,提高穩定性;
同時也讓編譯器更好地為我們優化代碼。const常量編譯時就會代替,避免了讀其地址浪費時間。
6.
函數形參用const &,避免拷貝
7.
訪問容器中的元素,用迭代器比下標訪問好
迭代器為訪問各種容器提供了統一的方法。
8
迭代器的自增自減,++放前面比后面好
9.
如果有多重if
if(score > 90)
cout<<"優秀"<<endl;
else if(score > 80)
cout<<"良好"<<endl;
else if(score>70)
cout<<"一般"<<endl;
else
cout<<"不及格"<<endl;
若數據量多為不及格,這樣效率就低,應該把判斷不及格放到最前面
1.整型運行速度快於浮點型,所以能用整型的地方盡量不要用浮點型計算,尤其是除法!移位運算的效率比一般的乘除法效率高!
2.盡量少調用簡單函數,例如一個數的平方,直接 a*a,而不要用pow(a,2),pow( )函數的效率很低!
3.容器函數不適合進行數據的插入、刪除,但出入棧的效率高;節點函數不適合進行數據的定點訪問,但是數據的插入、刪除效率高。
4.直接復制的效率高於間接賦值,例如int a(12)高於int a=12(也高於int a;a=12)。
5.雙重循環中,最好將大循環放在內層,內存的申請最好放在外面,避免多次重復開辟!重點:多次重復操作是效率低下最為直接的表現!
6.代碼盡量簡潔(滿足功能),結構越復雜效率越低!
7.數據操作最好直接使用內存數據操作(直接操作buffer)。
8.直接使用現有的封裝函數很方便,但是效率不是最好的,簡單的功能實現,最好還是自己寫源碼。
9.內存需要用多少就開辟多少,冗余數據會降低效率,也不便於管控內存!
10.盡量避免不同數據類型相互操作,減少數據的類型強轉!
11.處理不同事件,需要顯著提高效率,可以開啟多線程(非常直觀有效)!
12.數據在內存上操作的效率是非常高的!盡量避免數據在硬盤上操作,例如,沒有特殊要求,不要將內存的數據每次都保存到本地硬盤。
---優化代碼,一定要懂數據結構和算法分析!
13.代碼前后的防呆很重要!但是過多不必要的防呆(明顯不出現的判別式!)會嚴重降低程序的判定效率。
14.要有良好的“單元測試”習慣,不斷優化細小“單元”,提高小模塊的效率。
15.熟練使用VS自帶的性能分析模塊,分析程序中不同代碼段的時間分配情況。
16.對於if判斷語句,預判為“真”的概率高的語句放在前面,其它根據概率依次放在后面,減少判定的次數。
17.能不用靜態變量的,盡量不要用靜態變量!可以的話,使用局部變量,也不提倡沒有必要的靜態局部變量!
18.圖像數據的計算,避免直接用OpenCV的數據類型,而使用buffer數據(將mat型轉化為unsigned char型)。
19.空間換時間。目前電腦的內存都很大,可以考慮多開辟內存空間,將大模塊分散並列計算,最好不要在循環體內重復開辟。
20.for循環內的代碼量過大,應該將其拆成多個for循環,這里涉及到了CPU緩存問題!
21.當for循環體系很大時,特別注意循環內的內存開辟問題!能在循環外開辟的內存空間,絕對不能在循環內重復開辟(重復釋放),經測試,內外開辟的效率可以相差數十倍!
22.當需要遍歷整個Mat類(opencv中矩陣形式)時,切記不要使用 .at< >( , )單點訪問的形式!最好是指針的形式 .ptr( ),遇到vector的矩陣向量,最好使用Mat類自帶的高階通道參數 CV_8UC(n),其中n可達到512!,然后采用指針的形式進行數據的訪問,效率會大大提高,在我的測試中效率會提高60倍!
23.若平台支持OpenMP,可以采用高度並行的OpenMP進行編程,但是要注意數據的獨立性,否則會出現問題!理論上主機有多少核就可以加速幾倍!在我的測試中,主機有12核,加速可以達到7倍左右!OpenMP編碼有時需要更改代碼的結構,看起來比較麻煩,但是對於計算機來說,越是規范的代碼,運行效率越高!
24.對於文件流操作,最好用傳統的c語言指令,例如,采用fprintf對數據進行輸入輸出,其速度一般較C++指令快。
25.盡量避免直接使用OpenCV的矩陣乘法,矩陣乘法的效率較低,若矩陣維度較小,可以自行拆成算式計算。在我的測試中,直接乘法計算比矩陣乘法效率高8倍左右!
26.算法調優的小技巧:C代碼較C++代碼的執行效率高;直接調用成熟的封裝庫有時還沒有自己編寫的代碼效率高;充分利用計算機的並行計算比單一的串行計算要強很多;越是簡單的代碼片段並不一定執行效率高,代碼要對計算機來說要簡潔易懂;調優后的執行效率通常比之前的要提速近百倍!
27.對於速率較高的數據緩存,可以預先開辟內存buffer,數據緩存的時候就直接采用memcpy。一邊開辟內存,一邊進行緩存可能會遇到數據丟失的情況!
28.向系統申請內存時,要特別注意容量!當程序加大申請內存較多時,要記得隨時釋放內存。針對系統自行申請的內存(例如std::vector),最好在內存使用完后,自行調用析構函數釋放內存(例如,std::vector vec可用 vec.~vector()手動釋放 ),而不要等到程序結束后,系統自己去釋放,否則累積的內存足以讓程序崩潰!注意malloc申請的需要先進行free釋放。
29.一般算法程序寫好后需要不停地測試優化,為了區別debug模式和release模式,需要在程序中設置判定語句(#if _DEBUG /*測試語句*/ #endif)以示區別不同版本,所有的調試代碼均可在debug下運行,而不會出現在release模式下,這樣可以非常方便切換模式,而不影響代碼結構。
1.將反復使用的數據存放在全局變量里面。
需要重復使用的數據,比如加載的圖片,CSV文件等數據,存放在全局變量里面,每次加載DLL時,只加載一次,直到卸載DLL,這些數據一直保持在內存中,避免重復加載,經過測試,這樣處理之后,漏裝檢測的時間由2.5S降低到了1.5S,因為反復讀取文件,圖片是一個很消耗時間的操作,要盡量避免。
全局變量的使用參考文獻:
https://blog.csdn.net/guoqianqian5812/article/details/52397930
2.使用多線程
如果程序中有可以同時進行的代碼,譬如五道算術題,大家每人算一道,最后只花費1/5的時間就解決了5道題,多線程就是這個思路,現代的CPU都是多核的,就相當於可以同時算五道題目,一旦你的代碼使用了多線程,恐怕你再也回不到單線程的時代了。
有一段程序花費300ms,我們開了32個多線程之后花費16ms,加速了20倍,更別提GPU動輒百倍的加速效果。
多線程參考文獻:
https://www.cnblogs.com/wangguchangqing/p/6134635.html
3.用a++和++a,a–,--a
通常使用自加、自減指令和復合賦值表達式(如a-=1及a+=1等)都能夠生成高質量的程序代碼,編譯器通常都能夠生成inc和dec之類的指令,而使用a=a+1或a=a-1之類的指令,有很多C編譯器都會生成二到三個字節的指令。
4.減少除法運算
無論是整數還是浮點數的運算,除法都會比較耗時,所以最后將除法運算等效成乘法運算。例如:a/b>20可改為a>b*20,可以簡化程序的運行時間。
5.盡量減少值傳遞,多用引用來傳遞參數。
至於其中的原因,相信大家也很清楚,如果參數是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)
- 1
- 2
- 3
- 4
其中若使用第一個函數(值傳遞),則在參數傳遞和函數返回時,需要調用string的構造函數和析構函數兩次(即共多調用了四個函數),而其他的三個函數(指針傳遞和引用傳遞)則不需要調用這四個函數。因為指針和引用都不會創建新的對象。如果一個構造一個對象和析構一個對象的開銷是龐大的,這就是會效率造成一定的影響。
然而在很多人的眼中,指針是一個惡夢,使用指針就意味着錯誤,那么就使用引用吧!它與使用普通值傳遞一樣方便直觀,同時具有指針傳遞的高效和能力。因為引用是一個變量的別名,對其操作等同於對實際對象操作,所以當你確定在你的函數是不會或不需要變量參數的值時,就大膽地在聲明的前面加上一個const吧,就如最后的一個函數聲明一樣。
同時加上一個const還有一個好處,就是可以對常量進行引用,若不加上const修飾符,引用是不能引用常量的。
舉個例子,我們使用值傳遞和指針傳遞的方式傳遞一張10M的圖片,使用值傳遞會多花費0.1S的時間。假如我們使用了10次值傳遞,將會增加1S的運行時間。
6.循環引發的討論1(循環內定義,還是循環外定義對象)
請看下面的兩段代碼:
代碼1:
ClassTest CT;
for(inti = 0; i < 100; ++i)
{
CT = a;
//do something
}
- 1
- 2
- 3
- 4
- 5
- 6
代碼2:
for(inti = 0; i < 100; ++i)
{
ClassTest CT = a;
//do something
}
- 1
- 2
- 3
- 4
- 5
你會覺得哪段代碼的運行效率較高呢?代碼1科學家是代碼2?其實這種情況下,哪段代碼的效率更高是不確定的,或者說是由這個類ClassTest本向決定的,分析如下:
對於代碼1:需要調用ClassTest的構造函數1次,賦值操作函數(operator=)100次;對於代碼2:需要高用(復制)構造函數100次,析構函數100次。
如果調用賦值操作函數的開銷比調用構造函數和析構函數的總開銷小,則第一種效率高,否則第二種的效率高。
大部分情況下我們建議循環外定義,也就是方法一
7.循環引發的討論2(避免過大的循環)
現在請看下面的兩段代碼,
代碼1:
for(inti = 0; i < n; ++i)
{
fun1();
fun2();
}
- 1
- 2
- 3
- 4
- 5
代碼2:
for(inti = 0; i < n; ++i)
{
fun1();
}
for(inti = 0; i < n; ++i)
{
fun2();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
注:這里的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次即可。
8.局部變量VS靜態變量
很多人認為局部變量在使用到時才會在內存中分配,也就是儲存單元,而靜態變量在程序的一開始便存在於內存中,所以使用靜態變量的效率應該比局部變量高,其實這是一個誤區,使用局部變量的效率比使用靜態變量要高。
這是因為局部變量是存在於堆棧中的,對其空間的分配僅僅是修改一次esp寄存器的內容即可(即使定義一組局部變量也是修改一次)。而局部變量存在於堆棧中最大的好處是,函數能重復使用內存,當一個函數調用完畢時,退出程序堆棧,內存空間被回收,當新的函數被調用時,局部變量又可以重新使用相同的地址。當一塊數據被反復讀寫,其數據會留在CPU的一級緩存(Cache)中,訪問速度非常快。而靜態變量卻不存在於堆棧中。
可以說靜態變量是低效的
9.避免使用多重繼承
在C++中,支持多繼承,即一個子類可以有多個父類。書上都會跟我們說,多重繼承的復雜性和使用的困難,並告誡我們不要輕易使用多重繼承。其實多重繼承並不僅僅使程序和代碼變得更加復雜,還會影響程序的運行效率。
這是因為在C++中每個對象都有一個this指針指向對象本身,而C++中類對成員變量的使用是通過this的地址加偏移量來計算的,而在多重繼承的情況下,這個計算會變量更加復雜,從而降低程序的運行效率。而為了解決二義性,而使用虛基類的多重繼承對效率的影響更為嚴重,因為其繼承關系更加復雜和成員變量所屬的父類關系更加復雜。
10.將小粒度函數聲明為內聯函數(inline)
正如我們所知,調用函數是需要保護現場,為局部變量分配內存,函數結束后還要恢復現場等開銷,而內聯函數則是把它的代碼直接寫到調用函數處,所以不需要這些開銷,但會使程序的源代碼長度變大。
所以若是小粒度的函數,如下面的Max函數,由於不需要調用普通函數的開銷,所以可以提高程序的效率。
inline Max(inta, intb)
{
returna>b?a:b;
}
- 1
- 2
- 3
- 4
什么是內聯函數
https://blog.csdn.net/zengxiaosen/article/details/73784922
11.多用直接初始化
與直接初始化對應的是復制初始化,什么是直接初始化?什么又是復制初始化?舉個簡單的例子,
ClassTest ct1;
ClassTest ct2(ct1); //直接初始化
ClassTest ct3 = ct1; //復制初始化
- 1
- 2
- 3
那么直接初始化與復制初始化又有什么不同呢?直接初始化是直接以一個對象來構造另一個對象,如用ct1來構造ct2,復制初始化是先構造一個對象,再把另一個對象值復制給這個對象,如先構造一個對象ct3,再把ct1中的成員變量的值復制給ct3,從這里,可以看出直接初始化的效率更高一點,而且使用直接初始化還是一個好處,就是對於不能進行復制操作的對象,如流對象,是不能使用賦值初始化的,只能進行直接初始化。可能我說得不太清楚,那么下面就引用一下經典吧!
以下是Primer是的原話:
“當用於類類型對象時,初始化的復制形式和直接形式有所不同:直接初始化直接調用與實參匹配的構造函數,復制初始化總是調用復制構造函數。復制初始化首先使用指定構造函數創建一個臨時對象,然后用復制構造函數將那個臨時對象復制到正在創建的對象”,還有一段這樣說,“通常直接初始化和復制初始化僅在低級別優化上存在差異,然而,對於不支持復制的類型,或者使用非explicit構造函數的時候,它們有本質區別:
ifstream file1("filename")://ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private
- 1
- 2
注:如還對直接初始化和復制初始化有疑問,可以參考一下前面的一篇文章:
C++直接初始化與復制初始化的區別深入解析,里面有有關直接初始化和復制初始化的詳細解釋。
12.盡量少使用dynamic_cast
dynamic_cast的作用是進行指針或引用的類型轉換,dynamic_cast的轉換需要目標類型和源對象有一定的關系:繼承關系。 實現從子類到基類的指針轉換,實際上這種轉換是非常低效的,對程序的性能影響也比較大,不可大量使用,而且繼承關系越復雜,層次越深,其轉換時間開銷越大。在程序中應該盡量減少使用。