自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

讓我們一起揭秘代碼效率真相

開發(fā) 前端
本篇文章我們將繼續(xù)分析C++各種操作的效率,包括不同類型變量的存儲效率,使用智能指針、循環(huán)、函數(shù)參數(shù)、虛函數(shù)、數(shù)組等的效率,以及如何做針對性優(yōu)化,或選擇更有效的替代方案。

 [[383104]]

本文轉(zhuǎn)載自微信公眾號「程序喵大人」,作者程序喵大人 。轉(zhuǎn)載本文請聯(lián)系程序喵大人公眾號。

大家好,我是逐漸過氣的程序喵。

本篇文章我們將繼續(xù)分析C++各種操作的效率,包括不同類型變量的存儲效率,使用智能指針、循環(huán)、函數(shù)參數(shù)、虛函數(shù)、數(shù)組等的效率,以及如何做針對性優(yōu)化,或選擇更有效的替代方案。

詳細(xì)目錄看下圖:

類和結(jié)構(gòu)體

現(xiàn)今流行面向?qū)ο缶幊蹋瑐€人也認(rèn)為這是一種使代碼更加清晰和模塊化的方法。面向?qū)ο缶幊田L(fēng)格優(yōu)缺點(diǎn)明顯,優(yōu)點(diǎn)是:

  1. 如果變量是同一結(jié)構(gòu)體或類的成員,則一起使用的變量也存儲在一塊,這樣數(shù)據(jù)緩存更有效。
  2. 類成員變量不需要作為參數(shù)傳遞給類成員函數(shù),省去了參數(shù)傳遞的開銷。

缺點(diǎn)是:

  1. 一些程序員把代碼分成太多的小類,沒太大必要且效率低。
  2. 非靜態(tài)成員函數(shù)有一個this指針,它會作為隱式參數(shù)傳遞給函數(shù),這會產(chǎn)生一部分開銷,特別是在32位系統(tǒng)中,寄存器是稀缺資源,this指針會占用一個寄存器。
  3. 虛函數(shù)效率較低

類和成員函數(shù)的開銷其實(shí)并沒有特別大,如果面向?qū)ο箫L(fēng)格可以使程序結(jié)構(gòu)更加清晰,我們只要避免在程序最關(guān)鍵的部分使用太多的函數(shù)調(diào)用,就不要擔(dān)心它的開銷。

類的數(shù)據(jù)成員

當(dāng)創(chuàng)建類或結(jié)構(gòu)體的實(shí)例時,其數(shù)據(jù)成員按其聲明的順序連續(xù)存儲。大多數(shù)編譯器都會對結(jié)構(gòu)體進(jìn)行內(nèi)存對齊,這種對齊可能會在成員大小混合的結(jié)構(gòu)體或類中,造成未使用字節(jié)的空洞。

  1. struct S1 { 
  2.     short int a; // 2字節(jié) 
  3.     // 6個空洞 
  4.     double b; // 8 
  5.     int d; // 4 
  6.     // 4個空洞 
  7. }; 
  8. S1 ArrayOfStructures[100]; 

這里,在a和b之間有6個未使用的字節(jié),因?yàn)閎必須從一個能被8整除的地址開始。最后還有4個未使用的字節(jié)。這樣做的原因是,數(shù)組中S1的下一個實(shí)例必須從一個能被8整除的地址開始,以便將其b成員以8對齊。通過將最小的成員放在最后,未使用的字節(jié)數(shù)可以減少到2:

  1. struct S1 { 
  2.     double b; // 8 
  3.     int d; // 4 
  4.     short int a; // 2 
  5.     // 2個空洞 
  6. }; 
  7. S1 ArrayOfStructures[100]; 

這種重新排序使結(jié)構(gòu)變小了8個字節(jié),整個數(shù)組變小了800個字節(jié)。

通過重新排序數(shù)據(jù)成員,結(jié)構(gòu)對象和類對象通??梢宰冃 H绻淮_定一個結(jié)構(gòu)或它的每個成員有多大,可以使用sizeof,它的返回值包括對象末尾的任何未使用的字節(jié)。

如果成員相對于結(jié)構(gòu)體或類開頭的偏移量小于128,則訪問數(shù)據(jù)成員的代碼會更緊湊,因?yàn)樵撈屏靠梢员硎緸?位有符號的數(shù)字。如果相對于結(jié)構(gòu)體或類的開頭的偏移量是128字節(jié)或更大,那么偏移量必須表示為一個32位數(shù)字(指令集在8位到32位之間沒有偏移量)。例如:

  1. struct S2 { 
  2.     int a[100]; // 400 
  3.     int b; // 4 
  4.     int ReadB() {return b;} 
  5. }; 

b的偏移量是400。任何通過指針或成員函數(shù)(如ReadB)訪問b的代碼,都需要將偏移量編碼為32位數(shù)字。如果交換了a和b,則兩者都可以通過編碼為8位有符號數(shù)字的偏移量來訪問,或者根本沒有偏移量。這使代碼更緊湊,以便更有效地使用Cache。因此,建議在結(jié)構(gòu)或類聲明中,大數(shù)組和其他大對象排在最后,最常用的數(shù)據(jù)成員排在前面。如果不能在前128個字節(jié)內(nèi)包含所有數(shù)據(jù)成員,則將最常用的成員放在前128個字節(jié)中。

類的成員函數(shù)

每次聲明或創(chuàng)建類的新對象時,都會生成數(shù)據(jù)成員的新實(shí)例。但無論多少類的實(shí)例,成員函數(shù)只有一份。調(diào)用成員函數(shù)和使用結(jié)構(gòu)指針或引用來調(diào)用簡單函數(shù)一樣快。

  1. struct S3 { 
  2.     int a; 
  3.     int b; 
  4.     int Sum1() {return a + b;} 
  5. }; 
  6. int Sum2(S3* p) {return p->a + p->b;} 
  7. int Sum3(S3& r) {return r.a + r.b;} 

這三個函數(shù)Sum1, Sum2和Sum3,做的是完全相同的事情,而且它們的效率是一樣的。有些編譯器會為這三個函數(shù)生成完全相同的代碼。Sum1有一個隱式的this指針,它和Sum2和Sum3中的p和r做同樣的事情。讓函數(shù)成為類的成員,還是給它一個指向類或結(jié)構(gòu)的指針或引用,這只是編程風(fēng)格的問題。一些編譯器通過在寄存器中傳輸this指針,使Sum1在32位Windows中比Sum2和Sum3更有效率。

靜態(tài)成員函數(shù)不能訪問任何非靜態(tài)數(shù)據(jù)成員或非靜態(tài)成員函數(shù)。靜態(tài)成員函數(shù)比非靜態(tài)成員函數(shù)更快,因?yàn)樗恍枰猼his指針。如果成員函數(shù),它不需要任何非靜態(tài)成員訪問,可以通過將它們設(shè)為靜態(tài)函數(shù)來加快速度。

虛函數(shù)

虛函數(shù)用于實(shí)現(xiàn)運(yùn)行時多態(tài),多態(tài)是面向?qū)ο缶幊滔鄬τ诜敲嫦驅(qū)ο缶幊绦实偷闹饕蛑?。如果可以避免虛函?shù)的使用,那可以提高程序的運(yùn)行效率,一般情況下,可以考慮使用編譯時多態(tài)替代運(yùn)行時多態(tài)。關(guān)于虛函數(shù)為什么導(dǎo)致程序效率低,可以看我之前的文章:《》

RTTI

RTTI,運(yùn)行時類型識別,會向所有類對象添加額外的信息,所以效率不高,可以考慮關(guān)閉RTTI選項(xiàng)來提高程序效率。

繼承

派生類對象的實(shí)現(xiàn)方式,與包含父類和子類成員的簡單類對象相同。父類和子類的成員訪問速度相同。通常,我們可以認(rèn)為使用繼承幾乎不會對性能造成任何損失。

然而這些情況下,效率稍微會有所下降:

  • 子類包括父類的所有數(shù)據(jù)成員,即便子類不需要父類的數(shù)據(jù)成員。
  • 父類數(shù)據(jù)成員的大小添加到子類成員的偏移量中。訪問總偏移量大于127字節(jié)的數(shù)據(jù)成員的代碼稍微不那么緊湊。

繼承多個父類,可能會導(dǎo)致指向基類指針訪問派生類的對象時更復(fù)雜。我們可以通過在派生類中創(chuàng)建對象來避免多重繼承,即組合替代繼承:

  1. class B1; 
  2. class B2; 
  3. class D : public B1, public B2 { 
  4. public
  5.     int c; 
  6. }; 

換成這樣:

  1. class B1; 
  2. class B2; 
  3. class D : public B1 { 
  4. public
  5.     B2 b2; 
  6.     int c; 
  7. }; 

一般來說,只有當(dāng)繼承對程序的邏輯結(jié)構(gòu)有利時,才應(yīng)該使用繼承。

位域

位可以使數(shù)據(jù)更加緊湊。訪問位成員比訪問結(jié)構(gòu)體的普通成員效率低。如果大的數(shù)組可以以此來節(jié)省代碼空間,那么稍微犧牲點(diǎn)效率也未嘗不可。

使用<<和|操作組合位字段比單獨(dú)編寫成員更快。例如:

  1. struct Bitfield { 
  2.     int a:4; 
  3.     int b:2; 
  4.     int c:2; 
  5. }; 
  6. Bitfield x; 
  7. int A, B, C; 
  8. x.a = A; 
  9. x.b = B; 
  10. x.c = C; 

假設(shè)A、B和C的值太小,不會導(dǎo)致溢出,可以通過以下方式改進(jìn)這段代碼:

  1. union Bitfield { 
  2.     struct { 
  3.         int a:4; 
  4.         int b:2; 
  5.         int c:2; 
  6.     }; 
  7.     char abc; 
  8. }; 
  9. Bitfield x; 
  10. int A, B, C; 
  11. x.abc = A | (B<<4) | (C<<6); 

如果需要防止溢出可以這樣:

  1. x.abc = (A & 0x0F) | ((B & 3) << 4) | ((C & 3) <<6 ); 

函數(shù)重載

重載的函數(shù),只是作為不同的函數(shù)來處理,和普通函數(shù)相同,沒有任何性能代價,可放心使用。

運(yùn)算符重載

重載運(yùn)算符等同于函數(shù)。使用重載運(yùn)算符與使用普通函數(shù)的效率完全相同。帶有多個重載運(yùn)算符的表達(dá)式,會導(dǎo)致為中間結(jié)果創(chuàng)建臨時對象,這樣效率較低。例如:

  1. class vector { 
  2. public
  3.     float x, y; 
  4.     vector() {} 
  5.     vector(float a, float b) {x = a; y = b;} 
  6.     vector operator + (vector const &a) { 
  7.         return vector(x+a.x, y+a.y); 
  8.     }     
  9. }; 
  10. vector a, b, c, d; 
  11. a = b + c + d; // 產(chǎn)生了中間對象(b+c) 

可以通過加入以下操作來避免為中間結(jié)果(b+c)創(chuàng)建臨時對象:

  1. a.x = b.x + c.x + d.x; 
  2. a.y = b.y + c.y + d.y; 

在簡單情況下,大多數(shù)編譯器可能會自動進(jìn)行這種優(yōu)化。

模板

在編譯之前,模板的參數(shù)被它們的值所替換,這一點(diǎn)上,模板與宏相似。下面的例子說明了函數(shù)參數(shù)和模板參數(shù)的區(qū)別:

  1. // Example 7.46 
  2. int Multiply (int x, int m) { 
  3.     return x * m; 
  4. template <int m> 
  5. int MultiplyBy (int x) { 
  6.     return x * m; 
  7. int a, b; 
  8. a = Multiply(10,8); 
  9. b = MultiplyBy<8>(10); 

a和b都會得到值10 * 8 = 80。區(qū)別在于m傳遞到函數(shù)的方式。在這個簡單函數(shù)中,m在運(yùn)行時從調(diào)用方轉(zhuǎn)移到被調(diào)用方。但是在模板函數(shù)中,m的值在編譯時就被替換,這樣編譯器看到的是常量8而不是變量m。

模板參數(shù)相對于使用函數(shù)參數(shù)的優(yōu)點(diǎn)是避免了參數(shù)傳遞的開銷,缺點(diǎn)是編譯器需要為模板參數(shù)的每個不同值創(chuàng)建一個模板函數(shù)的新實(shí)例。如果在這個例子中,用許多不同的因子作為模板參數(shù)調(diào)用MultiplyBy,那么生成的代碼可能會變得非常大。

在上例中,模板函數(shù)比簡單函數(shù)快,因?yàn)榫幾g器知道它可以通過使用移位操作乘以2的冪。x*8被替換為x<<3,這樣更快。在簡單函數(shù)的情況下,編譯器不知道m(xù)的值,因此不能進(jìn)行優(yōu)化,除非函數(shù)可以內(nèi)聯(lián)。(在上面的例子中,編譯器能夠內(nèi)聯(lián)和優(yōu)化這兩個簡單的函數(shù)函數(shù),將80存于a和b中。但在更復(fù)雜的情況下,它可能做不了這種優(yōu)化)。

模板參數(shù)也可以是類型,想必大家也經(jīng)常使用這種類型不同的模板吧。模板之所以高效,是因?yàn)槟0鍏?shù)總是在編譯時解析。模板使源代碼更復(fù)雜,但不會使編譯后的代碼更復(fù)雜。一般來說,使用模板在運(yùn)行速度方面沒有開銷。

如果模板參數(shù)完全相同,兩個或多個模板實(shí)例將被連接到一個模板實(shí)例中。如果模板參數(shù)不同,那么編譯器會為每組模板參數(shù)生成一個實(shí)例。帶有許多實(shí)例的模板會使編譯后的代碼變大。模板的過度使用,會使代碼難于閱讀。如果模板只有一個實(shí)例,我們可以使用#define、const或typedef來代替模板形參。

模板可實(shí)現(xiàn)編譯時多態(tài),在某些情況下,我們可以使用編譯時多態(tài)替代運(yùn)行時多態(tài)。

線程

線程想必大家都知道,充分利用多核系統(tǒng)的最佳方法,是將任務(wù)劃分為多個線程。然后,每個線程都可以在自己的CPU內(nèi)核上運(yùn)行。

在優(yōu)化多線程程序時,需要考慮幾種開銷:

  1. 啟動和停止線程的成本:如果一個任務(wù)的執(zhí)行時間,比它啟動和停止線程的時間還要短,那就不要把它放到單獨(dú)的線程中。
  2. 上下文切換的成本:如果線程數(shù)量不超過CPU的數(shù)量,開銷最小。
  3. 線程間同步和通信的成本。信號量、互斥鎖等的開銷相當(dāng)大,如果兩個線程為了訪問同一資源而經(jīng)常相互等待,那么最好將它們放到一個線程中。在多個線程之間共享的變量須聲明為volatile,這可以防止編譯器將該變量存儲在寄存器中,而該寄存器在線程之間不共享。

異常和錯誤處理

關(guān)于異常和錯誤處理我之前寫過一篇文章你的c++團(tuán)隊還在禁用異常處理嗎?,大家可以看看,使用異常來處理錯誤是個很有效的方法,異常處理目的是以一種優(yōu)雅的方式檢測很少發(fā)生的錯誤,并從錯誤情況中恢復(fù)。使用異常處理有些缺點(diǎn)我們需要知道:

  • 打開exception選項(xiàng)會導(dǎo)致程序空間增大10%左右
  • 帶有try-catch的代碼運(yùn)行效率和普通代碼類似,但也肯定沒有普通代碼效率高(看編譯器優(yōu)化的程度)
  • 一旦發(fā)生異常,程序運(yùn)行速度明顯下降

某些明確不會產(chǎn)生異常的函數(shù)可以考慮加noexcept修飾,讓編譯器進(jìn)行最大程度的優(yōu)化。

如果不需要從錯誤中恢復(fù),則不需要進(jìn)行異常處理,建議使用系統(tǒng)的、經(jīng)過深思熟慮的方法來處理錯誤。

預(yù)處理指令

預(yù)處理指令,所有以#開頭的指令,在程序性能方面沒有任何開銷,因?yàn)樗鼈兪窃诔绦蚓幾g之前解析的。

#if指令對于支持使用同一源代碼的多個平臺或多個配置很有用。#if比if更高效,因?yàn)?if在編譯時解析,而if在運(yùn)行時解析。

定義常量時,#define指令等價于const定義。例如,#define ABC 123和const int ABC = 123; 同樣有效,因?yàn)樵诖蠖鄶?shù)情況下,優(yōu)化編譯器可以用整數(shù)常量的值替換整數(shù)常量。然而,在某些情況下,const int聲明可能占用內(nèi)存空間,而#define指令則不會占用內(nèi)存空間。使用宏有些時候比普通函數(shù)更有效。

命名空間

盡管使用命名空間吧,不用擔(dān)心,在速度方面沒有任何開銷。

上面分析了不同操作的效率以及如何針對性做一些優(yōu)化,在網(wǎng)上我也找到了一個圖,圖里列出了不同的操作占用的CPU時鐘周期:

我們可以仔細(xì)看看上圖,在編碼時選擇效率更高的操作。

參考資料

https://www.agner.org/optimize/

 

責(zé)任編輯:武曉燕 來源: 程序喵大人
相關(guān)推薦

2022-03-31 18:59:43

數(shù)據(jù)庫InnoDBMySQL

2021-08-27 07:06:10

IOJava抽象

2021-12-29 08:27:05

ByteBuffer磁盤服務(wù)器

2022-03-08 17:52:58

TCP格式IP

2021-07-15 07:23:28

Singlefligh設(shè)計

2021-11-26 07:00:05

反轉(zhuǎn)整數(shù)數(shù)字

2022-06-26 09:40:55

Django框架服務(wù)

2022-02-14 07:03:31

網(wǎng)站安全MFA

2016-09-06 10:39:30

Dell Techno

2022-02-14 10:16:22

Axios接口HTTP

2023-08-14 08:38:26

反射reflect結(jié)構(gòu)體

2022-07-10 23:15:46

Go語言內(nèi)存

2023-08-02 08:35:54

文件操作數(shù)據(jù)源

2022-08-01 07:57:03

數(shù)組操作內(nèi)存

2021-07-31 11:40:55

Openresty開源

2012-04-14 20:47:45

Android

2021-12-16 12:01:21

區(qū)塊鏈Libra貨幣

2021-02-20 08:05:35

代碼效率C++

2021-11-09 23:54:19

開發(fā)SMI Linkerd

2014-02-25 08:59:14

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號