官一下宣
Visual Studio 2019 v16.3和v16.4包含了對C++內聯器的一系列改進,其中包含這麼一條:具備對某些經過優化後的代碼進行內聯的能力,我們稱之為」Zipliner」。根據你的應用的不同,你可能會看到一些較小的代碼質量改進或者編譯時間的顯著縮短。
C2內聯器
Terry Mahaffey在之前的一篇文章」Visual Studio’s inlining decisions」中給出了VS內聯決策的概述。以下是對於這項改進比較相關的要點:
1. 內聯器是以遞歸的方式運行的,在某些情況下,它可能會重新做它之前做過的事情。內聯決策是上下文敏感的並且它並不總是對同一個函數做出相同的決策。
2. 內聯器在工作過程中十分關注資源的消耗。所以它會在生成的可執行文件的大小和運行時性能之間做出艱難的平衡。
3. 在內聯器看來,它所面對的世界都是」未經優化」的。它並不了解隱藏在代碼背後的邏輯細節,比如Copy propagation和Dead control path之類的。
現代C++
不幸的是,現有的大多數代碼模式和慣用法都引入了泛型編程的理念。考慮如下的代碼(來自Eigen庫):
它會調用下面代碼中的innerSize函數:
outerStride實例化後實際上只是返回它的一個數據成員。因此,它會是一個極好的內聯展開對象。但是對於模塊中每一次對outerStride的調用,編譯器都必須評估和展開多達18個被它調用的函數。
這將顯著的影響編譯器的編譯時間,並大大增加了內聯器的資源消耗。另外,更壞的消息是,對」rows」和」cols」的調動都是內聯的,即使他們它們並不會被執行。
如果編譯器只是對2行成員執行內聯優化,那就會好得多了,如下所示:
對經過優化的IR進行內聯
對於有些函數,內聯器現在可以對經過優化的IR進行展開,而不需要先獲取IR然後重新展開被調用函數。這會帶來兩個好處:一是可以加快調用樹的展開,二是有助於內聯器更加精準地評估其資源消耗。
第一,優化器在第一次編譯時將作出快速展開outerStride的決定(還記得嗎,c2.dll會在編譯函數的調用者之前編譯函數本身)。然後,內聯器可能會將對outerStride的實例化調用替換為現場訪問。
這個能被快速展開的函數對象通常是一個沒有本地變量的」葉子函數」,最多只會有兩個不同的參數或者全局變量等。在實際的項目中,他們可能是一些getters或者setters。
帶來的好處
還有很多類似於outerStride的例子。如果你的代碼大量的使用到了Eigen庫,你會發現編譯時間有顯著的縮短,編譯吞吐量將有25%到50%的提升。
新的Zipliner處理提升編譯性能,同時也能讓內聯器更準確的評估資源消耗。一直以來,Eigen庫的開發者都知道MSVC不會內聯他們設立的規範(詳情請看EIGEN_STRONG_INLINE)。Zipliner的出現應該會讓他們緩解這種焦慮。
試試看!
Zipliner默認在VS2019 v16.3中啟用,同時在v16.4中得到了一些新的優化。
總結
時至今日,我還是沒有足夠的勇氣去探索編譯器的世界。還是寫幾年HelloWorld把基礎打好先吧。
感謝閱讀(畢竟不是太容易懂)。