編寫規則的挑戰

回報問題 查看原始碼 夜間 7.2 7.1 7.0 6.5 6.4

本頁面概略說明特定問題和挑戰 方便您編寫高效率的 Bazel 規則

摘要規定

  • 假設:以正確性、處理量、使用便利性和延遲時間
  • 假設:大型存放區
  • 假設:類似 BUILD 的說明語言
  • 歷史經驗:載入、分析和執行之間有硬性區分為 已過時,但仍會影響 API
  • 內建函式:遠端執行和快取是硬的
  • 內建函式:使用變更資訊進行正確快速的漸進式建構作業 需要異常的程式設計模式
  • 本質上:避免二次耗時和記憶體用量很困難

假設

下方是針對建構系統產生的假設, 正確性、使用便利性、處理量和大規模的存放區。 詳列這些假設,並提供指南,確保 如何以高效率的方式撰寫規則

以正確性、處理量、使用便利性和延遲時間

我們假設建構系統必須是 並按照漸進式建構進行調整針對特定來源樹狀結構的 無論輸出樹狀結構什麼,都應一律是相同的建構 喜歡在第一個估算作業中,這意味著 Bazel 必須知道 傳入特定建構步驟的輸入值,讓它可以重新執行該步驟 (如有) 輸入內容會有什麼變化Bazel 會因錯誤外洩而受限, 並且忽略特定種類的資訊 (例如建構的日期 / 時間), 例如檔案屬性變更採用沙箱機制 防止讀取未宣告的輸入檔案,協助確保正確性。側邊 系統的內建限制、有一些已知的正確問題 其中大多數與 Fileset 或 C++ 規則有關 如要解決關聯問題,可用 Apriori 這類關聯規則學習技術和演算法我們長期以來會努力修正這些問題。

建構系統的第二個目標是具備高處理量。我們 將部門內部可以做的事永久推進 分配給遠端執行服務如果遠端執行 服務超載,任何人都無法完成工作

接下來是讓客戶更容易使用。從多種正確方法使用相同 (或 類似) 遠端執行服務的足跡,我們會選擇 使用起來更方便

延遲時間代表從開始建構到取得預定目標所需的時間 結果 (可能是通過/失敗測試產生的測試記錄),或 系統會顯示訊息,指出 BUILD 檔案有錯字。

這些目標通常會重疊延遲時間就好比處理量 確保遠端執行服務的正確性。

大型存放區

建構系統必須大規模運作,存放區為大型存放區 比例代表這款硬碟無法容納單一硬碟 在幾乎所有開發人員電腦上都能完成完整結帳。中等規模版本 必須讀取及剖析數萬個 BUILD 檔案,並評估 好幾十萬隻青蛙雖然理論上來說 我們目前還無法在單一電腦中下載 BUILD 檔案 可提供合理的作業時間與記憶體容量因此,請務必確保 BUILD 檔案 供您單獨載入及剖析

類似於建構的說明語言

在本範例中,我們假設的設定語言是 與程式庫和二進位規則宣告中的 BUILD 檔案大致類似 與其相互依存BUILD 檔案可獨立讀取及剖析, 我們甚至可以盡量避免查看來源檔案 (除了 存在)。

歷史古蹟

Bazel 版本之間存在差異,因而造成難題。 ,將會在以下各節中概述。

載入、分析和執行作業之間的硬性區隔已過時,但仍會影響 API

從技術層面來說,這足以讓規則知道 一項動作。不過, 原始 Bazel 程式碼集對載入套件有嚴格區隔性 使用設定 (基本上是指令列旗標) 來分析規則 然後執行任何動作這項差異仍然是 Rules API 的一部分 即使 Bazel 的核心已不再需要這項技術 (詳情請見下文)。

這表示規則 API 需要宣告規則 介面 (其擁有的屬性、屬性類型)此外還有 以下例外狀況:API 允許自訂程式碼在載入階段執行 會計算輸出檔案的隱含名稱以及屬性的隱含值。適用對象 例如,名為「foo」的 java_library 規則會間接產生名為 「libfoo.jar」,可以自建構圖表中的其他規則參照。

此外,分析規則後也無法讀取任何來源檔案或檢查 動作的輸出結果;而是需要產生 建構步驟和輸出檔案名稱的圖形 (僅透過規則決定) 與其依附元件

本質上可解釋

有一些與內生性質的關係會使得撰寫規則變得棘手, 我們會在以下各節中說明一些最常見的原因。

遠端執行和快取很困難

遠端執行和快取可藉由下列做法,縮短大型存放區中的建構時間: 與在單一應用程式中執行建構相比,執行建構作業大約是兩條大小 這類機制更為快速然而,它需要執行的規模相當驚人 遠端執行服務專為處理大量要求而設計 其後,通訊協定會謹慎避免不必要的往返作業 為服務端提供不必要的工作

此時通訊協定要求建構系統知道所有輸入內容的輸入內容 並採取相應行動接著,建構系統會計算不重複的動作 指紋,並要求排程器進行快取命中如果系統在快取中找到了所需資料 排程器會回覆輸出檔案的摘要;主要是 稍後將透過摘要解決不過,這會對 Bazel 施加限制 來預先宣告所有輸入檔案

使用變更資訊來正確快速進行漸進式建構作業,需要不尋常的程式設計模式

根據我們的判斷,Bazel 必須知道所有輸入內容 才能偵測該建構步驟 保持最新狀態套件載入和規則分析也是如此 且設計了 SkyFrame 來處理這個問題 SkyFrame 是一種圖形程式庫和評估架構 目標節點 (例如 'build //foo with 這些選項'),並細分為 接著,系統會評估及組合這些組成部分 結果。在這個過程中,SkyFrame 會讀取套件、分析規則,並 就會執行動作

在每個節點中,SkyFrame 會明確追蹤任何使用特定節點計算的節點 從目標節點到輸入檔案 以及 SkyFrame 節點)。在記憶體中明確呈現這張圖表 可讓建構系統找出受到特定政策影響的節點 變更輸入檔案 (包括建立或刪除輸入檔案), 將輸出樹狀結構還原至預期狀態所需的最低工作量。

過程中,每個節點都會執行依附元件探索程序。每項 節點可以宣告依附元件,並使用這些依附元件的內容 宣告進階依附元件原則上,此物件對應於 每個節點的執行緒模型但中型建構包含數百個 使用現行的 Java 程式,成千上百個 SkyFrame 節點不容易 技術 (由於歷史因素,目前我們已使用 Java 沒有精簡的執行緒,也沒有連續。

而 Bazel 會使用固定大小的執行緒集區。但這意味著 宣告尚未提供的依附元件,我們可能必須取消該依附元件 並重新啟動評估作業 (可能在其他執行緒中),當依附元件處於 廣告。換言之,節點不應過度執行此動作;換 依序宣告 N 依附元件的節點有可能重新啟動 N 次, 會耗用 O(N^2) 時間相反地,我們的目標是 依附元件 有時需要重新整理程式碼 限制重新啟動次數。

請注意,Rules API 目前不支援這項技術。 規則 API 仍在定義時,使用的是載入、分析、 以及執行階段不過,最基本的限制是 必須先完成架構才能追蹤 對應的依附元件無論建構系統使用何種語言 或在寫入規則時撰寫規則 (不一定 規則作者不得使用 SkyFrame。若是 Java,意味著避免使用 java.io.File 和 和兩者的任何程式庫支援依附元件的程式庫 插入這些低階介面時,仍需正確設定 SkyFrame。

這強烈建議避免將規則作者暴露在全語言執行階段中 一開始就做出決定意外使用這類 API 的危險範圍極大: 過去有幾個 Bazel 錯誤是由使用不安全的 API 的規則所導致, 但這些規則是由 Bazel 團隊或其他領域專家撰寫。

要避免二次工程時間和記憶體使用量相當困難

更糟的是,除了 SkyFrame 的規定外, 包括過去使用 Java 的限制,以及 Rules API 的過時程度 導致使用者意外產生二次性時間或記憶體用量 能夠根據程式庫和二進位規則 執行所有建構系統中的問題這裡共有兩個 造成二次記憶體消耗量的常見模式 二次耗用的時間)。

  1. 圖書館規則鏈結 - 以程式庫規則 A 的鏈結為例,依附於 B、依附於 C,且 依此類推然後,我們要計算 例如 Java 執行階段類別路徑 每個程式庫。簡單來說,我們可能會採用標準名單導入方式。不過 這已造成二次型記憶體消耗量:第一個程式庫 包含一個項目,在類別路徑中,第二項和第三個三項等等 開啟,總共有 1+2+3...+N = O(N^2) 個項目。

  2. 二元規則取決於相同程式庫規則: 假設有一組二進位檔都依附於同一個程式庫 例如,如有多個測試規則 程式庫程式碼假設在 N 項規則中,有一半的規則是二元規則。 其他半型程式庫規則現在,假設每個二進位檔都會 針對程式庫規則的遞移性關閉計算所計算的部分屬性,例如 Java 執行階段類別路徑,或 C++ 連結器指令列例如 可以展開 C++ 連結動作的指令列字串表示法。不適用 N/2 元素的複本是 O(N^2) 記憶體。

自訂集合類別,避免二次複雜

Bazel 主要是對這兩種情況造成影響,因此我們推出一組 可以藉由自訂的集合類別,有效壓縮記憶體中的資訊 避免在各個步驟中複製內容這些資料結構幾乎全都 稱為語意 depset (在內部實作中也稱為 NestedSet)。大部分 並減少 Bazel 過去幾年的記憶體用量 改用 Depset 來取代先前使用過的內容

遺憾的是,使用 depset 無法自動解決所有問題。 特別是即使沒有重複計算,每次定時也都會重新 二次耗用的時間。在內部,NestedSet 也有一些輔助方法 以促進與一般集合類別互通;很遺憾 不小心將 NestedSet 傳送到其中一種方法,導致 並重新引入二次記憶體消耗量