午夜网站国产欧美_加勒比视频亚洲无码_91亚洲人人在字幕国产_18禁止美女爆乳免费网站_被消防员c哭高h野外糙汉动漫_午夜精品视频在线无码_gogowww人体大胆裸体午液_2021自拍偷区亚洲综合第一页_国产欧美一区二区精品性色超碰_99國產精品無碼

Hi,您好,歡迎來到西安盛圖軟件科技有限公司!

C++性能優(yōu)化大局觀

發(fā)布時間:2024-01-30 13:41:33

C++ 可算是一種聲名在外的編程語言了。這個名聲有好有壞。從好的方面講,C++ 性能非常好,哪個編程語言性能好的話總忍不住要跟 C++ 來單挑一下。從壞的方面講,它是臭名昭著的復雜、難學、難用。

不管說 C++ 是好還是壞,不可否認的是,C++ 仍然是一門非常流行且非常具有活力的語言。繼沉寂了十多年后發(fā)布語言標準的第二版——C++11——之后,C++ 以每三年一版的頻度發(fā)布著新的語言標準,每一版都在基本保留向后兼容性的同時提供著改進和新功能。

雖然在語言領域,也有Rust這樣的新語言在向 C++ 發(fā)起挑戰(zhàn),但是,不可否認的是,C++ 仍然是面向性能的領域里的編程語言王者。我甚至不認為 C++ 在性能方面次于 C——在極致追求速度時,C++ 可以比 C 更強,而 C 相比 C++ 的主要優(yōu)點是更加簡單:不管是學習、使用,還是產(chǎn)生的二進制代碼的體積上。

今天,我們就來大略討論一下,C++ 是如何做到高性能的。

Bjarne 老爺子認為 C++ 最主要的特點在于以下兩方面的關注:

跟 C 語言一樣,C++ 提供非常底層的數(shù)據(jù)操作能力,為開發(fā)者提供了靈活性。跟“高級”語言一樣,C++ 提供了強大的抽象能力(可以說超越了大部分語言)。而且,相比 C,C++ 要安全得多。在語言誕生的初期就是如此,現(xiàn)在就更不用說了。

C++ 的類型系統(tǒng)比 C 更加嚴格,因此雖然一直有 C++ 是 C 的超集的說法,這個說法嚴格來說從來就沒成立過。最近(2023 年)碰到過一個程序崩潰的案例,簡化來講,就是開發(fā)者使用了一個 char 的二維數(shù)組(char names[MAX_NAMES] [MAX_NAME_LEN]),然后把它傳給了一個接收 char** 參數(shù)的函數(shù)……這代碼當然是錯的,但 C 編譯器雖然給了個告警,但編譯還是沒有失敗。如果這是 C++ 代碼的話,那編譯器就會直接報告錯誤,不給通過了。

而第二點,零開銷抽象,對于 C++ 的性能至關重要。我們有很多的抽象機制,同時,使用這些抽象機制并不會帶來額外的開銷。在某些情況下,使用這些機制,反而有“負開銷”—— “使用者”可以非常安全地使用這門語言,即可獲得極高的性能。同時,C++ 還給予 了“定制者”根據(jù)自己的需求來寫出更貼近使用場景的庫的能力,可以進一步方便“使用者”。

當然,定制對程序員的技能有非常高的要求。初學 C++ 的更需要掌握 C++ 的標準庫的使用——用好標準庫,就能獲得非常不錯的性能。正如高德納大神的名言的完整版:

而 C++ 已經(jīng)提供相當多的機制,可以允許我們很容易地獲取高性能,在很多場景下遠遠超過高德納所說的 12%。

舉個例子, C++ 標準庫的sort和 C 標準庫的qsort:在關閉優(yōu)化時,在某一測試場景下得到了 1:2.5 的性能差異,C++ 似乎要慢不少;但一旦打開 -O2(允許內(nèi)聯(lián))時,兩者的性能差異突變成 3.5:1,C++ 的性能比 C 高出了好幾倍!這就是所謂的“負開銷”了。C++ 的代碼比 C 的更簡單、更直觀,性能還更高。原因自然就是 C++ 的函數(shù)對象和模板機制允許編譯器更好地進行內(nèi)聯(lián),從而產(chǎn)生更加高性能的代碼。

因此,學會用好 C++ 的第一步是用好 C++ 的基本機制和標準庫,了解標準庫的不同機制的性能開銷,包括時間和空間。

任何情況下學習 C++,第一需要了解的就是析構函數(shù)和 RAII(resource acquisition is initialization)慣用法。對,雖然 C++ 誕生時名字是“帶類的 C”,但類和面向對象并不等同,對面向對象編程的支持并不是 C++ 的最重要特性。C++ 的自定義類型的最特別之處不在多態(tài),而在對其行為的定制上——最重要的就是對象銷毀時應該做些什么。析構函數(shù)和析構函數(shù)帶來的 RAII 慣用法,是 C++ 里最重要的特性,也是用 C++ 進行資源管理的關鍵。

重載是另外一個非常重要的 C++ 特性。除了你不用在名字上區(qū)分 process_char、process_string、process_int 帶來的方便性外,它對泛型編程也很重要,還對現(xiàn)代 C++ 的一個基本特性“移動語義”非常重要。刨除語法上的細節(jié),本質上來說,移動語義就是讓程序員可以方便地區(qū)分會繼續(xù)使用的對象和以后不再使用的對象,允許對后者使用構造函數(shù)和賦值運算符的重載來“竊取”其中的資源。對于一個普通的 vector,拷貝的開銷是 O(n) 或更高(如果 vector 成員是容器或其他具有高拷貝開銷的對象),但移動開銷通常(是,只是通常;不過通常你也不會遇到這種例外的特殊情況)是 O(1),常數(shù)復雜度。這就是我們在 C++ 里高效傳遞對象的一種常見方式了。

C++ 標準庫里最常用的組件恐怕就是 string 和各種容器了。它們都對移動進行了優(yōu)化。當然,除了這個基本的性能點外,容器都有各自的特殊性能點,比如不同情況下的插入性能差異。這些都是需要學習的地方。

比如,vector 在尾部插入性能比較好,在中間插入性能比較差。不過,更進一步的是,你需要知道,尾部插入性能好的前提條件是元素的類型對移動有很好的實現(xiàn),并且移動構造函數(shù)聲明成了 noexcept!如果你實現(xiàn)了開銷為 O(1) 的移動構造函數(shù),但忘了把它聲明為 noexcept,那仍然是白搭,vector 的尾部插入仍然有性能問題。

又如,list 不管從開頭、結尾還是中間插入,都具有很高的性能。但是,對于相同元素的 list 和 vector,list 的遍歷性能可能要差一個數(shù)量級。這個原因就不完全是 C++ 的知識點了,而是跟硬件的緩存組織相關。如果我們關心性能的話,這些都是需要了解的地方

前面我們已經(jīng)提到過模板,而 string 和容器也都是模板,行為可以通過模板參數(shù)來進行定制,并允許高效的內(nèi)聯(lián)優(yōu)化。模板當然是 C++ 里比較復雜的一個地方,但基本的使用則相當簡單:vector 就是一個放 int 的 vector,用起來跟一個普通的類沒有區(qū)別——只是模板創(chuàng)建者的工作簡單了,不需要手工為不同的類型創(chuàng)建不同的類。

用好 C++、在項目中獲得令人滿意的性能 當然不止上面這一些。最基本的,我們還需要了解標準庫算法,并合適地使用并發(fā)和并行來充分利用硬件。在本文中我們暫且就不展開了。

當我們用熟了 C++ 之后,慢慢地,我們就會不再滿足于 C++ 標準庫這一“制式武器”。我們會尋找適合自己的第三方庫,甚至自己造輪子來滿足項目的特定需求。此時,我們就需要進一步了解 C++ 的高級特性。我們需要了解模板的進一步細節(jié),尤其是特化。我們需要了解 SFINAE 和模板元編程。我們需要了解 constexpr 和它帶來更方便的編譯期編程。C++ 的使用者也許可以暫時不關心這些問題,但定制者,或者說項目里的框架搭建者和工具提供者,必須去了解 C++ 的這些高級特性,為你的項目提供扎實的基礎。

舉個例子,C++ 的標準庫提供了 list,雙向鏈表。這個庫沒啥問題,但在某些使用場景下,它的時間和空間開銷都不令人滿意,比如我們的對象除了正常的管理,還需要一個額外的 LRU(least recently used)算法來拋棄其中最老的項。你當然可以使用 list,但每次插入操作都需要插入一個對象,除了有堆內(nèi)存分配開銷,你還需要考慮在這個 list 里到底存什么。也許用智能指針?情況是不是越搞越復雜了?

這種情況下,最合理的選擇是使用某種 intrusive_list,侵入式的鏈表,不需要在每次插入或刪除時進行內(nèi)存管理。C++ 標準庫沒有提供這個功能。你可以使用 Boost 里提供的容器,或者自己寫一個新的。對于這個例子,Boost 多半就足夠好了。但總可能出現(xiàn)一些現(xiàn)成庫解決不了的問題的,這時候,利用 C++ 的高級特性來自己造輪子就是一件非常自然的事。我們可以做到既有合適的定制,同時用法又跟已有的容器相似,沒有額外的學習成本。

或者,也許你希望使用分配器來創(chuàng)建一個容器內(nèi)存池,來提供對內(nèi)存的使用效率。這在 C++ 里也是非常容易完成的,只要你了解合適的定制機制。根據(jù)洋蔥原則,你可以不管這些定制點,直接用 C++,這樣最簡單;也可以把標準庫“切開”,以自己最喜歡的方式來拼接定制使用——當然,這種做法確實跟切洋蔥一樣,很容易就會哭鼻子的。但它確實能幫助你獲得最高的可能性能。


以上為本次所有分享內(nèi)容


上一篇:動靜態(tài)庫的創(chuàng)建 | 使用 | 加載
下一篇:Linux 設置定時任務常用的三種方法

歡迎登錄盛圖科技

歡迎注冊盛圖科技

已有賬號,立即登錄