前言
伴隨著互聯網的快速發展,數據的規模及軟件的復雜程度呈指數級增長,帶來的硬件資源開銷、能源消耗也越來越大。為了控制硬件成本,也為了支持更大的并發量,軟件性能的優化工作也越來越重要。接下來,本文將從“充分了解 CPU”、“深入理解編譯器”、“對你的代碼負責”三個方面詳細、全面介紹下軟件性能優化的原理及相關方法。文章最后給出總結:性能優化的時機、性能優化的注意事項及性能優化需要的工具有哪些。
1 充分了解 CPU
系統的 CPU 理論知識太多、太復雜,包括 CPU 架構、運算單元、記憶單元、控制單元、數據總線、指令周期、多核、超線程等等,這里只講三個性能優化時常見的概念:cache、寄存器和多線程。
- cache
關于 cache,你只要記住一點:CPU 在執行任務的時候,從 cache 讀取數據的速度遠遠大于從內存讀取數據的速度。而程序在執行時,會有數據 cache miss 和指令 cache miss。cache 的容量決定了有多少代碼和數據可以放到 cache里面,我們優化程序的目標是把程序(代碼段)盡可能放到 cache 里面,讀取數據時,盡可能做到 cache 里的數據都是要用的。涉及的手段有:
結構體變量字節對齊
預取(prefetch):提前獲取下一階段程序執行需要的數據
函數重排:獲取程序運行軌跡,重排二進制目標文件(elf 文件)里的代碼段
函數冷熱分區
- 寄存器
控制函數入參個數(對有的硬件而言,當函數入參不超過 4 個時,入參是放在寄存器里的,寄存器讀取速度那是相當快滴)。
- 多線程
多線程是充分利用 CPU 多核優勢,避免 I/O 時 CPU 閑置。這里一個重要的性能瓶頸點是:鎖。如何定位鎖的開銷?這里可以大致給你個數據,一般單次加解鎖 100 cycles,spinlock 或者 cas 更快點。多線程場景下,如果 CPU 利用率上不去,而系統吞吐量也上不去,那么大概率時鎖導致的性能下降。
2 深入理解編譯器
避免過多的函數跳轉,請使用內聯函數和函數宏;
編譯器自帶的優化項,比如 gcc -O3 等;
32 位環境里面用 64 位 counter 很顯然會影響性能,所以除非必要,最好別用;
使用靜態內存替代堆內存,,可以做到提前分配;
利用編譯器自帶的優化工具,做好分支預測。
3 對你的每一行代碼負責
算法的重要性相必大家都很清楚了,每個人都能想到這一點,好的算法能帶來性能數量級上提升。這里,推薦一種最簡單也是最有效的手段:查表,內存足夠的話,數組性能最高。
不要做額外的事情,特別是無用的事情,避免重復計算。
內存池能大量減少內存碎片。
理清多條件判斷的順序,盡量在最外層條件判斷時就能有結果。
除了上述三點之外,還有 I/O 優化、網絡通信時間優化、硬件加速(使用專有硬件,將部分軟件功能下沉到硬件)、優化代碼流程等方法。
總結:
- 性能優化的時機
當你遇到性能瓶頸時,你就不得不做性能優化了。在一些軟件競賽時,特別明顯,除了比拼算法(軟件大賽題目一般都是 NP-hard 問題,主要看誰的程序模擬的輪次更多、更有效),剩下的就是要如何榨干 CPU 的每一個 TICK。
- 性能優化的工具
善用 perf(linux 自帶的工具,采集程序運行時的線程、函數等開銷)及生成火焰圖(圖形顯示函數調用棧及 CPU 占用率)。具體的使用方法,各位同學自行學習哈,還有 top -H、ps -aux 等常用命令。
- 性能優化的注意事項
性能優化時,不能破壞原有的程序功能,更不能引入新的 bug。
要平衡好程序性能和代碼美觀度及可讀性之間的關系。
性能優化要按照軟件重構的步驟:
一個時刻只戴一頂帽子;可觀察行為保持;小步前進
最后,引用大神 Kent Beck 的一句話作為文章的結尾:“你可能不會成為一個偉大的程序員,但是你可以成為一個具有偉大習慣的好程序員。”