天窗

Bazel 的平行評估和成效增幅模型。

資料模型

資料模型包含以下項目:

  • SkyValue,也稱為節點。SkyValues 是不可變更的物件,包含建構期間建構的所有資料和建構輸入內容。例如輸入檔案、輸出檔案、目標和設定的目標。
  • SkyKey:用來參照 SkyValue 的簡短名稱,例如 FILECONTENTS:/tmp/fooPACKAGE://foo
  • SkyFunction:根據金鑰和相依節點建構節點。
  • 節點圖表。包含節點間依附元件關係的資料結構。
  • Skyframe:漸進式評估架構 Bazel 的程式碼名稱是以此為基礎。

評估

建構作業會評估代表建構要求的節點 (這是我們正在設法處理的狀態,但過程中有很多舊版程式碼)。首先,系統會找到其 SkyFunction,並使用頂層 SkyKey 的鍵呼叫。接著,此函式會要求評估要評估頂層節點的節點,進而產生其他函式叫用,以此類推,直到達到分葉節點 (通常是代表檔案系統的輸入檔案的節點)。最後,我們得到頂層 SkyValue 的值、一些副作用 (例如檔案系統的輸出檔案),以及建構相關節點之間依附元件的有向非循環圖。

如果 SkyFunction 無法事先告知所有需要執行工作的節點,便可在多個票證中要求 SkyKeys。舉一個簡單的例子,會評估某個輸入檔案節點,而最後判定為符號連結:函式會嘗試讀取檔案,判斷這是符號連結,進而擷取代表符號連結目標的檔案系統節點。但其本身可以是符號連結,在這種情況下,原始函式也必須擷取其目標。

函式會在程式碼中,由 SkyFunction 介面以及由名為 SkyFunction.Environment 的介面提供給該函式的服務所呈現。函式的功能包括:

  • 請呼叫 env.getValue 來要求評估另一個節點。如果可用節點,系統會傳回其值;否則會傳回 null,且函式本身應傳回 null。如果是後者,系統會評估相依節點,然後再次叫用原始節點建構工具,但這次相同的 env.getValue 呼叫會傳回非 null 的值。
  • 呼叫 env.getValues(),要求評估多個其他節點。這基本上相同,只是系統會平行評估相依節點。
  • 叫用期間進行計算
  • 具有副作用,例如將檔案寫入檔案系統。請小心,兩個不同的功能並不會介入,一般而言,寫入副作用 (資料從 Bazel 流出) 是可以的,但讀取副作用 (資料流入 Bazel,且沒有已註冊的依附元件) 並不算,因為這些是未註冊的依附元件,因此可能會導致漸進式建構作業不正確。

SkyFunction 實作項目不應以要求依附元件 (例如直接讀取檔案系統) 的方式存取資料,因為這會導致 Bazel 無法為已讀取的檔案註冊資料依附元件,進而導致漸進式建構作業不正確。

當函式擁有足夠的資料來執行工作時,則應傳回表示已完成的非 null 值。

這項評估策略有許多優點:

  • 密封。如果函式只會依據其他節點來要求輸入資料,Bazel 可以確保在輸入狀態相同時,會傳回相同的資料。如果所有天空功能具有確定性,這表示整個建構作業也會具有確定性。
  • 正確且成效增幅實驗。如果記錄了所有函式的所有輸入資料,Bazel 只會將輸入資料變更時需要失效的確切節點組合失效。
  • 平行處理任務數量。由於函式只能透過要求依附元件的方式彼此互動,因此未依附的函式可以平行執行,且 Bazel 保證結果與依序執行時相同。

成效增幅

由於函式只能依據其他節點存取輸入資料,Bazel 可以建構完整的資料流程圖,從輸入檔案到輸出檔案,並且使用這項資訊來僅重新建立實際需要重建的節點,也就是已變更輸入檔案集的反向遞移關閉。

具體來說,您可以運用「由下而上」和「由上而下」兩種成效增幅策略,何者是最佳選擇,取決於依附元件圖表的外觀。

  • 在由下而失效的無效期間,在建立圖表且一組變更的輸入內容後,所有節點都會連帶失效,而這取決於變更的檔案。如果我們知道之後會再次建構相同的頂層節點,就很適合採用這種做法。請注意,「由下而上」無效需要對先前版本的所有輸入檔案執行 stat(),才能判斷這些錯誤是否已變更。您可以使用 inotify 或類似的機制瞭解已變更的檔案,藉此改善這一點。

  • 在「由上往下」失效期間,系統會檢查頂層節點的遞移關閉情形,且只有這些節點會持續保留遞移性閉合。如果已知目前的節點圖表較大,但在下一個版本中只需要一小部分的圖表:由下而無效會使第一個版本的較大圖形失效,這與僅追蹤第二個版本的較小圖表不同。

我們目前只執行由下而上無效作業。

我們使用「變更裁舊方式」來進一步提高成效:如果節點失效,但在重新建構時,會發現新節點與舊值相同,且因這個節點的變更而失效的節點將「恢復」。

這種做法很實用,例如當 C++ 檔案中的註解發生了變更時,從該檔案產生的 .o 檔案將會相同,因此無需再次呼叫連接器。

增量連結 / 編譯

這個模型的主要限制在於,節點的撤銷是完全或不可靠的做法:當依附元件變更時,系統一律會從頭開始重新建立相依節點,即使存在更好的演算法,也會根據變更來變動節點的舊值。以下列舉幾個實用的範例:

  • 增量連結
  • .jar 中的單一 .class 檔案變更時,我們理論會修改 .jar 檔案,而不是從頭開始建構。

Bazel 現階段並不支援這類功能 (我們有部分指標支援增量連結,但這類機制並未在 SkyFrame 中實作) 有兩大好處:我們只能提升效能,結果難以保證異動的結果與乾淨的重建成果相同,而且 Google 的價值建構可以重複執行。

目前我們只要分解一個昂貴的建構步驟,並進行局部重新評估,就能達到足夠的效能:這個做法會將應用程式中的所有類別分割為多個群組,然後分別對這些類別進行 DEX 處理。如此一來,如果群組中的類別並未變更,則不必重做 dex 處理。

對應至 Bazel 概念

以下是 Bazel 用來執行建構作業的 SkyFunction 實作概略說明:

  • FileStateValuelstat() 的結果。針對存在的檔案,我們也會計算額外資訊,以偵測檔案變更。這是 SkyFrame 圖表中的最低層級節點,沒有任何依附元件。
  • FileValue。負責處理檔案實際內容和/或解析路徑的任何用途,取決於對應的 FileStateValue 和任何需要解析的符號連結 (例如 a/bFileValue 需要 a 的解析路徑和 a/b 的解析路徑)。FileStateValue 之間的區別非常重要,因為在某些情況下 (例如評估檔案系統的 Glob (例如 srcs=glob(["*/*.java"])) 檔案內容實際上則不需要)。
  • DirectoryListingValue。基本上是 readdir() 的結果。依附於與目錄相關聯的 FileValue
  • PackageValue。代表經過剖析的 BUILD 檔案。依附於相關聯 BUILD 檔案的 FileValue,以及遞移用於解析套件中 glob 的任何 DirectoryListingValue (在內部代表 BUILD 檔案內容的資料結構)
  • ConfiguredTargetValue。代表已設定的目標,這是一組設定的目標,且一組在分析目標時產生的動作,以及提供給已設定且依附此目標的資訊。取決於對應目標所在的 PackageValue、直接依附元件的 ConfiguredTargetValues,以及代表建構設定的特殊節點。
  • ArtifactValue。代表建構作業中的檔案,可以是來源或輸出成果 (成果幾乎與檔案相同,並且會在實際執行建構步驟時用來參照檔案)。就來源檔案而言,它會取決於關聯節點的 FileValue,如果是輸出構件,則取決於產生成果的任何動作的 ActionExecutionValue
  • ActionExecutionValue。代表動作執行。依附於輸入檔案的 ArtifactValues。其執行動作目前包含在其天鍵中,這一點與天空鑰匙應該是較小的概念相牴觸。我們正在努力解決這項差異問題 (請注意,如果我們不在 SkyFrame 上執行執行階段,則不會使用 ActionExecutionValueArtifactValue)。