現代C++編譯器是否會根據debug期間運行所得的信息來進行優化?

C++採用AOT編譯模式,運行期間無法進行編譯了,那麼一些JIT能做的優化AOT不能做到。不過C++的編譯器有採用PGO模式,

看到Building with Profile-Guided Optimization這上面介紹了關於C++的PGO的簡單描述,說先通過第一次運行收集信息,然後第二次編譯的時候通過第一次運行收集到的一點一點的信息完成優化,感覺和JIT優化方法的優點類似。

但是想知道C++的這兩次編譯是發生在怎麼樣的時刻?比如是在開發者輸入編譯指令之後一次性的把兩個步驟都做了嗎?還是要開發者在開發過程中手動兩次編譯?那現代編譯器是否會根據Debug期間收集到的運行信息來對最終release版的二進位代碼進行優化呢?


關於AOT、JIT與PGO,以前寫的一篇東西應該正好跟這個問題相關:JIT編譯,動態編譯與自適應動態編譯 - 編程語言與高級語言虛擬機雜談(仮) - 知乎專欄

其中提到的.NET的MPGO功能就是在AOT編譯模型下的PGO的一種典型流程。

AOT編譯模型下的PGO,通常涉及的步驟有:

  1. 以profile模式來編譯程序:編譯器會知道要插入收集profile的邏輯到生成的代碼里;
  2. training run:使用profile模式的程序,輸入有代表性的數據來多次運行之,收集profile保存下來;
  3. 以PGO模式來編譯程序:結合收集到的profile,再編譯一次程序,這次使用profile信息來做優化。

所以回到題主的問題:

但是想知道C++的這兩次編譯是發生在怎麼樣的時刻?比如是在開發者輸入編譯指令之後一次性的把兩個步驟都做了嗎?還是要開發者在開發過程中手動兩次編譯?

實際操作常常是上述三個步驟都是手工完成的。當然可以寫腳本來自動化這些步驟,但這些步驟自身是少不了的。

重點在於:編譯器是沒辦法知道什麼是對該應用程序而言「有代表性的輸入數據」,所以training run必然得讓程序員自己來做。編譯器(或者說編譯器的驅動程序)最多只能提供些方便使用的選項,內部還是得做這三步。

那現代編譯器是否會根據Debug期間收集到的運行信息來對最終release版的二進位代碼進行優化呢?

這個腦洞開得有意思。其實換句話說,題主問的就是:現代編譯器能否在debug build里生成帶有收集profile邏輯的代碼,這樣debug程序的同時就自動收集了profile,然後在release build的時候就可以直接上PGO來生成優化的代碼了。

我是沒在現實中見過這樣做的系統。當然「理論上」是可以實現出這樣的東西的但是…

只是這個流程感覺略為奇怪:在開發過程中debug,通常是為了抓蟲,而抓了蟲之後就要修改代碼來修復問題。代碼一修改,之前收集的profile就很難利用了(在多數現實的系統了,代碼發生了變化的話profile直接就作廢了,因為要「容錯」地使用以前收集的profile是個非常麻煩的事情)。這麼一來,在debug過程中就算收集profile,也未必能用在後續的編譯優化中,感覺略浪費。

而且如果做PGO的時候,所利用的profile來自不具代表性的training run,那編譯出來的代碼性能可能還會比不用PGO更糟糕。Debug的時候常常會跑邊角用例(edge case),這樣收集下來的profile對PGO也是沒有幫助的。


沒有。

微軟的,Intel的,GCC,clang,arm的都沒這麼做。


推薦閱讀:

有沒有介紹LLVM的書籍可以推薦?最好是中文的
計算機是怎麼區分int類型和float類型的數據的?
編譯器為什麼在一次編譯過程中要儘可能的發現所有錯誤?
你覺得編譯程序中使用的關鍵技術都有哪些應用方向?請詳細說明。
如果想要寫一個C++或者面向對象的語言的一個簡單的編譯器,應該學習哪些領域的知識?

TAG:C | 編譯器 | 軟體調試 |