之前的“效能最佳化的一般策略及方法”一文中介紹了多種效能最佳化的方法。根據以往的專案經驗,開啟編譯器最佳化選項可能是立竿見影、成本最低、效果最好的方式了。
這麼說可能還不夠直觀,舉個真實的例子:我所參與的自動駕駛的專案中,無需修改任何程式碼,僅僅增加一個 -O2
選項,程序整體的 CPU loading 可以從 50% 降到 30% 左右,某些關鍵函式的執行時間可以從 1700us 降低到 700us 左右。
編譯器能最佳化能力遠比你想象中的強大!往後翻翻附錄,看看那些多到讓人眼花的最佳化選項你就知道,很多的人工最佳化都是不必要的,編譯器會做得更快,更好,更安全!人工最佳化,不僅會降低程式碼的可讀性和可維護性,而且非常容易引入 bug!
實際上,不管是 -O2
還是 -O3
,都是一組最佳化選項的集合,要知道具體做了什麼,可以透過 gcc/g++ 的 -c -Q --help=optimizers
引數
例如我用的 aarch64-unknown-nto-qnx7.1.0-g++ 編譯器,如果想知道加了 -O2
之後開啟了哪些最佳化項,可以透過以下 3 條命令:
$ aarch64-unknown-nto-qnx7.1.0-g++ -c -Q -O2 --help=optimizers > /tmp/O2-opts
$ aarch64-unknown-nto-qnx7.1.0-g++ -c -Q --help=optimizers > /tmp/O-opts
$ diff /tmp/O2-opts /tmp/O-opts | grep enabled
< -fdevirtualize [enabled]
< -finline-functions-called-once [enabled]
< -finline-small-functions [enabled]
< -foptimize-strlen [enabled]
< -freorder-blocks [enabled]
< -freorder-functions [enabled]
< -ftree-switch-conversion [enabled]
< -ftree-tail-merge [enabled]
...
隨便看了幾個,就足以感受到編譯器最佳化選項的強大:
- finline-xxx:行內函數,以避免函式呼叫開銷。順便提一句:程式碼中的
inline
關鍵字只是一個對編譯器的提示,編譯器會根據具體情況作出最佳的選擇,無論是否有inline
關鍵字 - fdevirtualize:嘗試把虛擬函式呼叫轉換為直接呼叫,以避免虛擬函式導致的額外開銷
- freorder-blocks:對函式中的程式碼塊重新排序,以減少分支數、提高程式碼區域性性
- freorder-functions:對物件中函式重新排序,以提升程式碼區域性性:把經常執行的函式放到 ".text.hot" 節,不常執行的函式放到 ".text.unlikely" 節
...
完整的最佳化項很多,具體每個選項的確切解釋需要檢視編譯器手冊。
小結
- 如果效能不理想,先檢查是否開啟了編譯器最佳化選項。這可能是最快、最有效的手段了。
- 編譯器能最佳化能力遠比你想象中的強大!
- 不要在沒有開啟最佳化選項的時候就開始盲目改程式碼,很多都是徒勞,甚至降低效能、引入 bug:編譯器最佳化會做得更快、更安全
- 如果開了最佳化選項,你的程式出現問題,不要懷疑編譯器,大機率是因為你的程式碼不規範,使用了 C/C++ “未定義”行為導致的
- 需要注意,在汽車領域中,對最佳化選項有一定的限制,比如我的專案中,編譯器的 Safety Manual 明確說明了最大隻支援
-O2
的最佳化等級
附錄
授人以漁
關於這個問題,我第一開始想到的是問 ChatGPT,但是得到的結果並不滿意。然後想到的是 RTFM!
man gcc
線上版本:https://manpages.org/gcc
搜尋關鍵字 /optimiz
,很快就找到了我要的答案:
gcc 支援的最佳化選項
Optimization Options
-faggressive-loop-optimizations -falign-functions[=n[:m:[n2[:m2]]]] -falign-jumps[=n[:m:[n2[:m2]]]] -falign-labels[=n[:m:[n2[:m2]]]]
-falign-loops[=n[:m:[n2[:m2]]]] -fno-allocation-dce -fallow-store-data-races -fassociative-math -fauto-profile -fauto-profile[=path]
-fauto-inc-dec -fbranch-probabilities -fcaller-saves -fcombine-stack-adjustments -fconserve-stack -fcompare-elim -fcprop-registers
-fcrossjumping -fcse-follow-jumps -fcse-skip-blocks -fcx-fortran-rules -fcx-limited-range -fdata-sections -fdce -fdelayed-branch
-fdelete-null-pointer-checks -fdevirtualize -fdevirtualize-speculatively -fdevirtualize-at-ltrans -fdse -fearly-inlining -fipa-sra
-fexpensive-optimizations -ffat-lto-objects -ffast-math -ffinite-math-only -ffloat-store -fexcess-precision=style -ffinite-loops
-fforward-propagate -ffp-contract=style -ffunction-sections -fgcse -fgcse-after-reload -fgcse-las -fgcse-lm -fgraphite-identity
-fgcse-sm -fhoist-adjacent-loads -fif-conversion -fif-conversion2 -findirect-inlining -finline-functions -finline-functions-called-once
-finline-limit=n -finline-small-functions -fipa-modref -fipa-cp -fipa-cp-clone -fipa-bit-cp -fipa-vrp -fipa-pta -fipa-profile
-fipa-pure-const -fipa-reference -fipa-reference-addressable -fipa-stack-alignment -fipa-icf -fira-algorithm=algorithm
-flive-patching=level -fira-region=region -fira-hoist-pressure -fira-loop-pressure -fno-ira-share-save-slots -fno-ira-share-spill-slots
-fisolate-erroneous-paths-dereference -fisolate-erroneous-paths-attribute -fivopts -fkeep-inline-functions -fkeep-static-functions
-fkeep-static-consts -flimit-function-alignment -flive-range-shrinkage -floop-block -floop-interchange -floop-strip-mine
-floop-unroll-and-jam -floop-nest-optimize -floop-parallelize-all -flra-remat -flto -flto-compression-level -flto-partition=alg
-fmerge-all-constants -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves -fmove-loop-invariants -fno-branch-count-reg
-fno-defer-pop -fno-fp-int-builtin-inexact -fno-function-cse -fno-guess-branch-probability -fno-inline -fno-math-errno -fno-peephole
-fno-peephole2 -fno-printf-return-value -fno-sched-interblock -fno-sched-spec -fno-signed-zeros -fno-toplevel-reorder -fno-trapping-math
-fno-zero-initialized-in-bss -fomit-frame-pointer -foptimize-sibling-calls -fpartial-inlining -fpeel-loops -fpredictive-commoning
-fprefetch-loop-arrays -fprofile-correction -fprofile-use -fprofile-use=path -fprofile-partial-training -fprofile-values
-fprofile-reorder-functions -freciprocal-math -free -frename-registers -freorder-blocks -freorder-blocks-algorithm=algorithm
-freorder-blocks-and-partition -freorder-functions -frerun-cse-after-loop -freschedule-modulo-scheduled-loops -frounding-math
-fsave-optimization-record -fsched2-use-superblocks -fsched-pressure -fsched-spec-load -fsched-spec-load-dangerous
-fsched-stalled-insns-dep[=n] -fsched-stalled-insns[=n] -fsched-group-heuristic -fsched-critical-path-heuristic -fsched-spec-insn-heuristic
-fsched-rank-heuristic -fsched-last-insn-heuristic -fsched-dep-count-heuristic -fschedule-fusion -fschedule-insns -fschedule-insns2
-fsection-anchors -fselective-scheduling -fselective-scheduling2 -fsel-sched-pipelining -fsel-sched-pipelining-outer-loops
-fsemantic-interposition -fshrink-wrap -fshrink-wrap-separate -fsignaling-nans -fsingle-precision-constant -fsplit-ivs-in-unroller
-fsplit-loops -fsplit-paths -fsplit-wide-types -fsplit-wide-types-early -fssa-backprop -fssa-phiopt -fstdarg-opt -fstore-merging
-fstrict-aliasing -fthread-jumps -ftracer -ftree-bit-ccp -ftree-builtin-call-dce -ftree-ccp -ftree-ch -ftree-coalesce-vars
-ftree-copy-prop -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -fcode-hoisting -ftree-loop-if-convert
-ftree-loop-im -ftree-phiprop -ftree-loop-distribution -ftree-loop-distribute-patterns -ftree-loop-ivcanon -ftree-loop-linear
-ftree-loop-optimize -ftree-loop-vectorize -ftree-parallelize-loops=n -ftree-pre -ftree-partial-pre -ftree-pta -ftree-reassoc
-ftree-scev-cprop -ftree-sink -ftree-slsr -ftree-sra -ftree-switch-conversion -ftree-tail-merge -ftree-ter -ftree-vectorize -ftree-vrp
-funconstrained-commons -funit-at-a-time -funroll-all-loops -funroll-loops -funsafe-math-optimizations -funswitch-loops -fipa-ra
-fvariable-expansion-in-unroller -fvect-cost-model -fvpt -fweb -fwhole-program -fwpa -fuse-linker-plugin -fzero-call-used-regs --param
name=value -O -O0 -O1 -O2 -O3 -Os -Ofast -Og
aarch64-unknown-nto-qnx7.1.0-g++ 加 -O2
相較於預設不加 -O2
增加的最佳化選項(完整列表)
$ aarch64-unknown-nto-qnx7.1.0-g++ -c -Q -O2 --help=optimizers > /tmp/O2-opts
$ aarch64-unknown-nto-qnx7.1.0-g++ -c -Q --help=optimizers > /tmp/O-opts
$ diff /tmp/O2-opts /tmp/O-opts | grep enabled
< -falign-labels [enabled]
< -fbranch-count-reg [enabled]
< -fcaller-saves [enabled]
< -fcode-hoisting [enabled]
< -fcombine-stack-adjustments [enabled]
< -fcompare-elim [enabled]
< -fcprop-registers [enabled]
< -fcrossjumping [enabled]
< -fcse-follow-jumps [enabled]
< -fdefer-pop [enabled]
< -fdevirtualize [enabled]
< -fdevirtualize-speculatively [enabled]
< -fexpensive-optimizations [enabled]
< -fforward-propagate [enabled]
< -fgcse [enabled]
< -fguess-branch-probability [enabled]
< -fhoist-adjacent-loads [enabled]
< -fif-conversion [enabled]
< -fif-conversion2 [enabled]
< -findirect-inlining [enabled]
< -finline-functions-called-once [enabled]
< -finline-small-functions [enabled]
< -fipa-bit-cp [enabled]
< -fipa-cp [enabled]
< -fipa-icf [enabled]
< -fipa-icf-functions [enabled]
< -fipa-icf-variables [enabled]
< -fipa-profile [enabled]
< -fipa-pure-const [enabled]
< -fipa-ra [enabled]
< -fipa-reference [enabled]
< -fipa-sra [enabled]
< -fipa-vrp [enabled]
< -fisolate-erroneous-paths-dereference [enabled]
< -flra-remat [enabled]
< -fmove-loop-invariants [enabled]
< -foptimize-sibling-calls [enabled]
< -foptimize-strlen [enabled]
< -fpartial-inlining [enabled]
< -fpeephole2 [enabled]
< -freorder-blocks [enabled]
< -freorder-functions [enabled]
< -frerun-cse-after-loop [enabled]
< -fsched-pressure [enabled]
< -fschedule-insns [enabled]
< -fschedule-insns2 [enabled]
< -fsection-anchors [enabled]
< -fshrink-wrap [enabled]
< -fsplit-wide-types [enabled]
< -fssa-phiopt [enabled]
< -fstore-merging [enabled]
< -fstrict-aliasing [enabled]
< -fthread-jumps [enabled]
< -ftree-bit-ccp [enabled]
< -ftree-builtin-call-dce [enabled]
< -ftree-ccp [enabled]
< -ftree-ch [enabled]
< -ftree-coalesce-vars [enabled]
< -ftree-copy-prop [enabled]
< -ftree-dce [enabled]
< -ftree-dominator-opts [enabled]
< -ftree-dse [enabled]
< -ftree-fre [enabled]
< -ftree-pre [enabled]
< -ftree-pta [enabled]
< -ftree-sink [enabled]
< -ftree-slsr [enabled]
< -ftree-sra [enabled]
< -ftree-switch-conversion [enabled]
< -ftree-tail-merge [enabled]
< -ftree-ter [enabled]
< -ftree-vrp [enabled]