Truffle 函數內聯方法

Truffle 為所有使用該框架建置的語言提供自動內聯。自 20.2.0 版本起,引入了一種新的內聯方法。本文說明了新方法如何運作、將其與傳統內聯方法進行比較,並說明新方法的設計選擇。

內聯 #

內聯是將對函數的呼叫替換為該函數主體的過程。這消除了呼叫的開銷,但更重要的是,它為編譯器的後續階段開啟了更多最佳化的機會。該過程的缺點是編譯的大小隨著每個內聯函數而增加。過大的編譯單元很難最佳化,並且安裝程式碼的記憶體是有限的。

由於所有這些原因,選擇要內聯的函數是在內聯函數的預期收益與增加編譯單元大小的成本之間進行權衡的精細過程。

Truffle 傳統內聯 #

Truffle 已經有一段時間採用內聯方法。不幸的是,這種早期的方法存在多個問題,主要問題是它依賴於呼叫目標中的 Truffle AST 節點數量來近似呼叫目標的大小。

AST 節點是呼叫目標實際程式碼大小的非常差的代理,因為無法保證單個 AST 節點會產生多少程式碼。例如,針對兩個整數相加的加法節點將產生的程式碼明顯少於針對整數、雙精度和字串相加的同一個節點(更不用說來自不同語言的不同節點和節點)。這使得無法有一個在所有 Truffle 語言中可靠運作的單一內聯方法。

關於傳統內聯的一個值得注意的事情是,由於它僅使用來自 AST 的資訊,因此內聯決策是在部分評估開始之前做出的。這表示我們只會部分評估我們決定要內聯的呼叫目標。這種方法的優點是,不會花費時間在未最終內聯的呼叫目標的部分評估上。另一方面,這會導致因內聯器做出的錯誤決策而頻繁出現編譯問題。例如,產生的編譯單元會太大而無法編譯。

與語言無關的內聯 #

新內聯方法的主要設計目標是在部分評估後,使用 Graal 節點(編譯器節點)的數量作為呼叫目標大小的代理。這是一個更好的大小代理,因為部分評估會刪除 AST 的所有抽象,並產生一個更接近呼叫目標實際執行的低階指令的圖形。這會在決定是否內聯呼叫目標時產生更精確的成本模型,並刪除 AST 攜帶的大部分特定於語言的資訊(因此得名:與語言無關的內聯)。

這是透過對每個候選呼叫目標執行部分評估,然後在那之後做出內聯決策(與在執行任何部分評估之前做出決策的傳統內聯相反)來實現的。將會完成的部分評估量以及將會內聯的量都由預算的概念控制。這些分別是「探索預算」和「內聯預算」,都以 Graal 節點計數表示。

這種方法的缺點是,我們需要對即使最終決定不內聯的呼叫目標執行部分評估。與傳統內聯相比,這會導致平均編譯時間顯著增加(約 10%)。

觀察和影響內聯 #

內聯器會維護一個內部呼叫樹,以追蹤對個別目標的呼叫狀態,以及做出的內聯決策。以下章節說明了呼叫樹中呼叫可以處於的狀態,以及如何找出在編譯期間做出了哪些決策。

呼叫樹狀態 #

內聯 節點 中的 呼叫樹 代表對特定目標的呼叫。這表示如果一個目標呼叫另一個目標兩次,我們會將其視為兩個節點,儘管它是相同的呼叫目標。

每個節點都可能處於以下六種狀態之一,在此說明

  • 已內聯 - 此狀態表示已內聯呼叫。最初,只有編譯的根目錄處於此狀態,因為它隱含地「已內聯」(即,屬於編譯單元的一部分)。
  • 已截斷 - 此狀態表示呼叫目標未部分評估,因此甚至未考慮進行內聯。這通常是因為內聯器已達到其探索預算的限制。
  • 已展開 - 此狀態表示呼叫目標已部分評估(因此,已考慮進行內聯),但已決定不進行內聯。這可能是由於內聯預算限制或目標被認為太昂貴而無法內聯(例如,內聯具有多個外向「已截斷」呼叫的小目標只會向編譯單元引入更多呼叫)。
  • 已移除 - 此狀態表示此呼叫存在於 AST 中,但部分評估移除了呼叫。這是比傳統內聯的優勢,傳統內聯會提前做出決策,且沒有辦法注意到這種情況。
  • 間接 - 此狀態表示間接呼叫。我們無法內聯間接呼叫。
  • 已中止 - 此狀態應該非常罕見,並被視為效能問題。這表示目標的部分評估導致 BailoutException,即,無法成功完成。這表示該特定目標存在一些問題,但我們不會中止整個編譯,而是將該呼叫視為無法內聯。

追蹤內聯決策 #

Truffle 提供一個引擎選項來追蹤呼叫樹的最終狀態,包括在編譯期間的許多附帶資料。此選項為 TraceInlining,並且可以透過所有常見方式進行設定:透過將 --engine.TraceInlining=true 新增至語言啟動器、如果執行執行客體語言(使用 Truffle 實作的語言)的常規 Java 程式,將 -Dpolyglot.engine.TraceInlining=true 新增至命令列,或 為引擎明確設定選項

以下是 JavaScript 函數的 TraceInlining 範例輸出

[engine] inline start     M.CollidePolygons                                           |call diff        0.00 |Recursion Depth      0 |Explore/inline ratio     1.07 |IR Nodes        27149 |Frequency        1.00 |Truffle Callees     14 |Forced          false |Depth               0
[engine] Inlined            M.FindMaxSeparation <opt>                                 |call diff       -8.99 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes         4617 |Frequency        1.00 |Truffle Callees      7 |Forced          false |Depth               1
[engine] Inlined              parseInt <opt>                                          |call diff       -1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          111 |Frequency        1.00 |Truffle Callees      0 |Forced           true |Depth               2
[engine] Inlined              M.EdgeSeparation                                        |call diff       -3.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes         4097 |Frequency        1.00 |Truffle Callees      2 |Forced          false |Depth               2
[engine] Inlined                parseInt <opt>                                        |call diff       -1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          111 |Frequency        1.00 |Truffle Callees      0 |Forced           true |Depth               3
[engine] Inlined                parseInt <opt>                                        |call diff       -1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          111 |Frequency        1.00 |Truffle Callees      0 |Forced           true |Depth               3
[engine] Inlined              parseInt <opt>                                          |call diff       -1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          111 |Frequency        1.00 |Truffle Callees      0 |Forced           true |Depth               2
[engine] Expanded             M.EdgeSeparation                                        |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes         4097 |Frequency        1.00 |Truffle Callees      2 |Forced          false |Depth               2
[engine] Inlined              parseInt <opt>                                          |call diff       -1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          111 |Frequency        1.00 |Truffle Callees      0 |Forced           true |Depth               2
[engine] Inlined              M.EdgeSeparation                                        |call diff       -3.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes         4097 |Frequency        1.00 |Truffle Callees      2 |Forced          false |Depth               2
[engine] Inlined                parseInt <opt>                                        |call diff       -1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          111 |Frequency        1.00 |Truffle Callees      0 |Forced           true |Depth               3
[engine] Inlined                parseInt <opt>                                        |call diff       -1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          111 |Frequency        1.00 |Truffle Callees      0 |Forced           true |Depth               3
[engine] Cutoff               M.EdgeSeparation                                        |call diff        0.01 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        0.01 |Truffle Callees      2 |Forced          false |Depth               2
[engine] Cutoff             M.FindMaxSeparation <opt>                                 |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      7 |Forced          false |Depth               1
[engine] Cutoff             M.FindIncidentEdge <opt>                                  |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees     19 |Forced          false |Depth               1
[engine] Cutoff             parseInt <opt>                                            |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      0 |Forced           true |Depth               1
[engine] Cutoff             parseInt <opt>                                            |call diff        0.98 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        0.98 |Truffle Callees      0 |Forced           true |Depth               1
[engine] Cutoff             A.Set <split-16abdeb5>                                    |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      0 |Forced          false |Depth               1
[engine] Cutoff             A.Normalize <split-866f516>                               |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      1 |Forced          false |Depth               1
[engine] Cutoff             A.Set <split-1f7fe4ae>                                    |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      0 |Forced          false |Depth               1
[engine] Cutoff             M.ClipSegmentToLine                                       |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      2 |Forced          false |Depth               1
[engine] Cutoff             M.ClipSegmentToLine                                       |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      2 |Forced          false |Depth               1
[engine] Cutoff             A.SetV <split-7c14e725>                                   |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      0 |Forced          false |Depth               1
[engine] Cutoff             A.SetV <split-6029dec7>                                   |call diff        1.00 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes            0 |Frequency        1.00 |Truffle Callees      0 |Forced          false |Depth               1
[engine] Inlined            L.Set <split-2ef5921d>                                    |call diff       -3.97 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          205 |Frequency        1.98 |Truffle Callees      1 |Forced          false |Depth               1
[engine] Inlined              set <split-969378b>                                     |call diff       -1.98 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          716 |Frequency        1.98 |Truffle Callees      0 |Forced          false |Depth               2
[engine] Inlined            set                                                       |call diff       -1.98 |Recursion Depth      0 |Explore/inline ratio      NaN |IR Nodes          381 |Frequency        1.98 |Truffle Callees      0 |Forced          false |Depth               1
[engine] inline done      M.CollidePolygons                                           |call diff        0.00 |Recursion Depth      0 |Explore/inline ratio     1.07 |IR Nodes        27149 |Frequency        1.00 |Truffle Callees     14 |Forced          false |Depth               0

傾印內聯決策 #

透過追蹤以文字形式提供的相同資訊也可在 IGV 傾印中取得。圖表是 Graal Graphs 群組中 Call Tree 子群組的一部分。圖表顯示內聯前後的呼叫樹狀態。

控制內聯預算 #

注意:內聯相關預算的預設值經過仔細選擇,並考慮了編譯時間、效能和編譯器穩定性。變更這些參數可能會影響所有這些。

與語言無關的內聯提供兩個選項來控制編譯器可以執行的探索量和內聯量。這些分別是 InliningExpansionBudgetInliningInliningBudget。兩者都以 Graal 節點計數表示。它們可以像任何其他引擎選項一樣進行控制(即,與「追蹤內聯決策」章節中所述的方式相同)。

InliningExpansionBudget 控制內聯器停止部分評估候選者的時間點。因此,增加此預算可能會對平均編譯時間產生非常負面的影響(特別是花費在執行部分評估的時間),但可能會提供更多的內聯候選者。

InliningInliningBudget 控制允許編譯單元由於內聯而擁有的 Graal 節點數量。增加此預算可能會導致更多候選者被內聯,這將導致更大的編譯單元。反過來,這可能會減慢編譯速度,尤其是在部分評估後的階段,因為較大的圖形需要更多時間才能最佳化。它也可能會提高效能(已移除的呼叫,最佳化階段具有更大的整體圖形)或損害效能,例如,當圖形太大而無法正確最佳化或完全無法編譯時。

與我們聯絡