這是一篇關於C++性能優化指南的學習筆記,主要是經過閱讀學習Kurt Guntheroth著的Optimized C++:Proven Techniques for Heightened Performance。 這是一本知識量和信息量很大的一本書書,書裏詳細介紹了影響C++程序性能的緣由,也給出了不少提升性能的優化策略。
書中不只講解了軟件和系統方面的相關內容,還涉及了計算機的硬件組成的基礎知識,使讀者能夠全面的瞭解計算機和程序設計。書中介紹的方法是具備通用性的,能夠延伸至其餘的編程語言,我的認爲這是一本能夠提高程序設計能力、感覺到優化之美的一本值得一讀的好書。
程序員
一、用好的編譯器並用好編譯器(支持C++11的編譯器,IntelC++(速度最快)、GNU的C++編譯器GCC/G++(很是符合標準),Visual C++(性能折中),clang(最年輕Mac OS x))。
二、使用更好的算法。
三、使用更好的數據結構(不一樣的數據結構在使用內存管理器的方式也有所不一樣)。
四、使用更好的庫(熟悉和掌握標準C++模板庫對於進行性能優化的開發員是必須的技能,Boost Project 和 Google Code 公開了不少有用的庫)。
五、減小內存分配和複製(減小對內存管理器的調用是一種很是有效的優化手段)。
六、優化內存管理(內存管理器的調度,豐富的API)。
七、移除計算(對於單條的C++語句進行優化)。
八、提升併發性(多個處理核心執行指令)。算法
一、計算機的物理組成自己對計算機性能的限制。
二、計算機的主內存是比較慢的(通往主內存的接口是限制執行速度的瓶頸(馮*諾伊曼瓶頸),(摩爾定理)每一年處理器的核心的數量都會增長,可是計算機的性能未必會提升,由於這些核心只是等待訪問內存的機會(內存牆memory wall))。
三、計算機內存的訪問方式(並不是以字節爲單位),某些內存訪問會比其餘的更慢(分爲一級高速緩存(cache memory)、二級高速緩存、三級高速緩存、主內存、磁盤上的虛擬內存頁)。
四、內存的容量是有限的,每一個程序都會與其餘程序競爭計算機資源,計算比作決定快。
五、在處理器中,訪問內存的性能開銷遠比其餘操做的性能開銷大,非對齊訪問所須要的時間是全部字節都在同一字節中的兩倍。
六、訪問頻繁使用的內存地址的速度比訪問非頻繁使用的地址快,訪問相鄰地址的內存的速度比訪問相互遠隔的地址的內存塊。
七、訪問線程間共享的數據比訪問非共享的數據資源慢不少。當併發線程共享數據時,同步代碼下降了併發量。
八、有些語句隱藏了大量的計算,從語句的外表上看不出語句的性能開銷會有多大。編程
一、90/10規則:一個程序會花費90%的運行時去執行10%的代碼。
二、只有正確且精確的測量纔是準確的測量。
三、分辨率不是準確性。
四、在Windows上,clock()函數提供了可靠的毫秒級的時鐘計時功能。在Windows8和以後的版本中,GetSystemTimePreciseAsfileTime()提供了亞微秒的計時功能。
五、計算一條C++語句對內存的讀寫次數,能夠估算出一句C++ 語句的性能開銷。數組
一、因爲字符串是動態分配內存的,所以它們的性能開銷很是大。它們在表達式中的行爲與值相似,它們的實現方式中須要大量的複製。
二、將字符串做爲對象而非值能夠下降內存分配和複製的頻率。
三、爲字符串預留內存空間能夠減小內存分配的開銷。
四、將指向字符串的常量引用傳遞給函數與傳遞值的結果幾乎同樣,可是更加高效。
五、將函數的結果經過輸出參數做爲引用返回給調用方會複用實參的存儲空間,這可能比分配新的存儲空間更加高效。
六、即便只是有時候會減小內存分配的開銷,仍然是一種優化。緩存
一、在C++程序中,亂用動態分配內存的變量是最大的「性能殺手」。
二、C++變量(每一個普通數據類型的變量;每一個數組,結構體或類實例)在內存中的佈局都是固定的,它們的大小在編譯時就已經肯定了。
三、每一個變量都有它的存儲期(生命週期),只有在這段時間內變量所佔用的存儲空間或者內存字節中的值纔是有意義的。爲變量分配內存的開銷取決於存儲期(靜態存儲期、線性局部存儲期、自動存儲期、動態存儲期)。
四、C++變量的全部者決定了變量何時會被建立,何時會被析構(變量全部權是一個單獨的概念,與存儲期不一樣)。動態變量的全部權必須有程序員執行並編寫在程序邏輯中,它不受編譯器控制,也不禁C++定義。具備強定義全部權的程序會比全部權分散的程序更高效。
五、在C++中,動態變量是由 new 表達式建立,由 delete 表達式釋放的。它們會調用C++標準庫的內存管理函數。
六、智能指針會經過耦合動態變量的生命週期與擁有該變量的智能指針的生命週期,來實現動態變量全部權的自動化。C++容許多個指針和引用指向同一個動態變量,共享了全部權的動態變量開銷更大。
七、靜態的建立類成員而且在有必要時採用「兩段初始化」,這樣能夠節省爲這些成員變量分配內存的開銷。
八、讓主指針來擁有動態變量,使用無主指針替代共享全部權。
九、從性能優化的角度上看,使用指針或是引用進行賦值和參數傳遞,或是返回指針或引用更加高效,由於指針和引用時存儲在寄存器中的。
十、當一個數據結構中的元素被存儲在連續的存儲空間中時,咱們稱這個數據結構爲扁平的,相比於通用指針連接在一塊兒的數據結構,扁平數據結構具備顯著的性能優點。性能優化
一、除非有一些因素放大了語句的性能開銷,不然不值得進行語句級別的性能優化,由於所能帶來的性能提高不大。
二、循環中的語句的性能開銷被放大的倍數是循環的次數。函數中的語句的性能開銷被放大的倍數是函數被調用的次數。被頻繁地調用的編程慣用法的性能開銷被放大的倍數是其被調用的次數。
三、從循環中移除不變性代碼(當代碼不依賴於循環的概括變量時,它就具備循環不變性),不過現代編譯器很是善於找出循環中被重複計算的具備循環不變性的代碼。
四、從循環中移除無謂的函數調用,一次函數調用可能會執行大量指令,這是影響程序性能的一個重要因素,若是一個函數具備循環不變性,那麼將它移除到循環外有助於改善性能。有一種函數永遠均可以被移動到循環外部,那就是返回值只依賴於函數參數並且沒有反作用的純函數。
五、從循環中移除隱含的函數調用;若是將函數簽名從經過值傳遞實參修改成傳遞指向類的引用和指針,有時候能夠在進行隱式函數調用時移除形參構建。
六、調用函數的開銷是很是小的,只是執行函數體的開銷可能很是大,若是一個函數被重複調用屢次則累積的開銷會變得很大。函數調用的開銷主要包括函數調用的基本開銷、虛函數的開銷、繼承中的成員函數調用、函數指針的開銷等。函數的調用開銷雖然很大,但正由於函數調用才實現了程序的一些複雜的功能。
六、調用操做系統的函數的開銷是高成本的。
七、內聯函數是一種有效的移除函數調用開銷的方法。數據結構
一、C++爲經常使用功能提供了一個簡潔的標準庫。
*肯定哪些依賴於實現的行爲,如每種數據類型的最大值和最小值。
*易於使用可是編寫和驗證都很繁瑣的可移植的超越函數(超越函數指的是變量之間的關係不能用有限次加、減、乘、除、乘方、開方運算表示的函數),如正弦函數和餘弦函數、對數函數和冪函數、隨機數函數等等。
*除了內存分配外,不依賴於操做系統的可移植的通用數據結構、如字符串、鏈表和表。
*可移植的通用數據查找算法、數據排序算法和數據轉換算法。
*以一種獨立於操做系統的方式與操做系統的基礎服務相聯繫的執行內存分配、操做線程、管理和維護時間以及流I/O等任務的函數。 多線程
二、使用C++標準庫的注意事項
*標準庫的實現中有bug,(標準庫和編譯器是單獨維護的,編譯器中也可能存在bug,標準需求的改變、責任的分散、計劃問題以及標準庫的複雜度都會不可避免地影響它們的質量)。
*標準庫的實現可能不符合C++標準,(庫的發佈計劃和編譯器是不一樣的,而編譯器的發佈計劃與與C++標準不一樣,一個標準庫的實現可能會領先或是落後於編譯器)。
*對於標準庫開發人員來講,性能並不是是最終要的事情,(由於庫會被長期使用,因此庫的簡單性和可維護性更加劇要)。
*庫的實現可能會讓一些優化手段失效,C++標準庫中的有些部分並不是是有用的。
*標準庫不如最好的原生函數,(標準庫沒有爲某些操做系統提供異步文件I/O等特性,性能優化人員只能經過調用原生函數,犧牲可移植性來換取運行速度)。併發
三、C++標準庫之因此提供這些函數和類,是由於要麼沒法以其餘方式提供這些函數和類,要麼這些函數和類被普遍地用於多種操做系統上。在對庫進行性能優化時,測試用例很是關鍵;接口的穩定性是可交付的庫的核心。異步
四、扁平繼承層次關係(多數抽象都不會有超高三層類繼承層次,一旦超高三次可能代表類的層次結構不夠清晰,其引入的複雜性會致使性能的降低)。
扁平調用鏈(絕大多數抽象的實現都不會超高三層嵌套函數的調用,在已經充分解耦的庫中是不會包含冗長的嵌套抽象調用鏈的)。
一、高效的算法是計算機科學一直研究的主題,計算機科學家十分重視算法和數據結構的研究,由於它是展現優化代碼的典型事例。當一個程序須要數秒內執行完畢,實際上卻要花費數小時時,惟一能夠用成功的優化方法可能就是選擇一種高效的算法了。算法是一個很是重要且不能簡而概之的主題,能夠參考《算法導論》,進行更深刻的學習。
二、優化模式
開發人員研究算法和數據結構的緣由之一是其中蘊含着用於改善性能的「思惟庫」,這些改善性能的通用技巧是很是的使用的,其中的一些模式也是數據結構、C++語言特性和硬件創新的核心。
* 預計算;能夠在程序早期,經過在熱點代碼前執行執行計算來將計算從熱點部分中移除。
* 延遲計算;經過在正真須要執行計算時才執行計算,可將計算從某些代碼路徑上移除。
* 批量處理;每次對多個元素一塊兒進行計算,而不是一次只對一個元素進行計算。
* 緩存;經過保存和複用高代價計算的結果來減小計算量,而不是重複進行計算。
* 特化;經過移除未使用的共性來減小計算量。
* 提升處理量;經過一次處理一大組數據來減小循環處理的開銷。
* 提示;經過在代碼中加入可能會改善性能的提示來減小計算量。
* 優化期待路徑;以期待頻率從高到低的順序對輸入數據或是運行時發生的事件進行測試。
* 散列法;計算可變成字符串等大型數據結構的壓縮數值映射(散列值)。在進行比較時,用散列代替數據結構能夠提升性能。
* 雙重檢查;經過先進行一項開銷不大的檢查,而後只在必要時才進行另一項開銷昂貴的檢查來減小計算量。
一、改善查找性能的工具箱,測量當前的實現方式的性能來獲得比較基準,識別出待優化的抽象活動,將待優化的活動分解爲組件算法和數據結構,修改或是替換那些可能並不是最優的算法和數據結構,而後進行性能測試以肯定修改是否有效果。
二、標準庫查找算法接受兩個迭代器參數:一個指向待查找序列的開始位置,另外一個則指向待查找序列的末尾位置(最後一個元素的下一個位置)。全部的算法還都接受一個要查找的鍵做爲參數以及一個可選的比較函數參數。
三、使用C++標準庫優化排序,在可以使用分而治之算法高效地進行查找以前,咱們必須先對序列容器排序,C++標準庫提供了兩種可以高效地對序列容器進行排序的標準算法——std::sort()和std::stable_sort()。
一、併發是多線程控制的同步執行,併發的目標不是減小指令執行的次數或是每秒訪問數據的次數,而是經過提升計算資源的使用率來減小程序運行的時間的。
二、有不少機制可以爲程序提供併發,其中有些基於操做系統或是硬件。C++標準庫直接支持線程共享內存的併發模型。
三、計算機硬件、操做系統、函數庫以及C++自身的特性都可以爲程序提供併發支持。
* 時間分隔;這是操做系統的一個調度函數,爲每一個程序都分配時間塊。操做系統是依賴於處理器和硬件的。它會使用計時器和週期性的中斷來調整處理器的調度。
* 虛擬化;虛擬化技術是讓操做系統將處理器的時間塊分配給客戶虛擬機,計算資源可以根據每臺客戶虛擬機上正在運行的程序的需求進行分配。
* 容器化;容器中包含了程序在檢查點的文件系統鏡像和內存鏡像,其主機是一個操做系統,可以直接提供I/O和系統資源。
* 對稱式多處理;是一種包含若干執行相同機器代碼並訪問相同物理內存的執行單元的計算機,現代多核處理器都是對稱式多處理器。使用正真的硬件併發執行多線程控制。
* 同步多線程;有些處理器的硬件核心有兩個或多個寄存器集,能夠相應地執行兩條或多條指令流。最高效第使用軟件線程的方法是讓軟件線程數量與硬件線程數量匹配。
* 多進程;進程是併發的執行流,這些執行流有它們本身的受保護的虛擬內存空間,進程之間經過管道、隊列、網路I/O或是其餘不共享的機制進行通訊,進程的主要優勢是操做系統會隔離各個進程,使其不會互相干擾影響。
* 分佈式處理;是指程序活動分佈在一組處理器上,這些處理器能夠不一樣。分佈式處理系統一般會被分解爲子系統,造成模塊化的,易於理解的和可以從新配置的體系結構。
* 線程;線程是進程中的併發執行流,它們之間共享內存;與進程相比,線程的優勢在於消耗的資源更少、建立和切換也更快。因爲進程中的全部線程都共享相同的內存空間,因此一個線程寫入無效的內存地址可能會覆蓋掉其餘線程的數據結構,致使線程奔潰或是出現不可預測的狀況。
* 任務;任務是一個獨立線程的上下文中可以被異步調用的執行單元,任務運行的基礎是線性池。基於任務的併發構建於線程之上,所以任務也具備線程的優勢和缺點。
四、若是沒有競爭,那麼一個多線程C++程序具備順序一致性,理想的競爭一塊短臨界區的核心數量是兩個。在臨界區中執行I/O操做沒法優化性能,可運行線程的數量應當少於或等於處理器核心數量。
以上是我關於C++性能優化指南的筆記,主要針對我我的的知識盲點、核心概念要點的簡單記錄;若有錯誤,但願你們批評指正!