細分建構效能

回報問題 查看來源

Bazel 很複雜,而且在建構過程中會執行許多不同的工作,其中有些作業可能會影響建構效能。本頁面會嘗試將部分 Bazel 概念對應至這些 Bazel 概念對建構效能的影響。我們另外提供了一些範例,說明如何透過擷取指標偵測建構效能問題,以及如何修正這些問題。希望您在調查建構效能迴歸時,可以運用這些概念。

簡潔與漸進式版本

簡潔的建構作業是從零開始建構所有項目,而漸進式建構作業會重複使用部分已完成的工作。

建議您分別查看簡潔和漸進式的建構作業,特別是在收集 / 匯總依賴 Bazel 快取狀態的指標 (例如建構要求大小指標) 時。它們也代表了兩種不同的使用者體驗。相較於從頭開始建構乾淨建構作業 (由於冷快取需要較長時間),由於開發人員對程式碼進行疊代作業,因此漸進式建構作業的執行頻率會比較高 (因為快取通常已暖機)。

您可以使用 BEP 中的 CumulativeMetrics.num_analyses 欄位來分類建構。如果是 num_analyses <= 1,表示這是簡潔的建構作業;否則,我們大致將其分類可能是漸進式的建構,因為使用者可能會改用不同的標記或不同的目標,從而可以有效地清理建構。如果是更嚴格的成效增幅定義,就可能必須採用經驗法則,例如檢查載入的套件數量 (PackageMetrics.packages_loaded)。

確定性建構指標可用來替代建構效能

由於某些指標具有非確定性 (例如 Bazel 的 CPU 作業時間或遠端叢集上的佇列時間),因此在測量建構效能時可能會遇到困難。因此,將確定性指標當做 Proxy 處理 Bazel 執行的工作量,進而影響其效能。

建構要求的大小可能會對建構效能產生重大影響。版本越大,在分析及建構建構圖形方面的工作越多。由於開發作業會增加/建立更多依附元件,因此建構項目自然會增加,建構成本也變得更高。

我們可以將這個問題分成各種建構階段,使用下列指標做為各階段工作的 Proxy 指標:

  1. PackageMetrics.packages_loaded:成功載入的套件數量。這裡的迴歸代表在載入階段中,需執行更多讀取及剖析其他 BUILD 檔案的工作。

    • 這通常是因為加入依附元件,且必須載入遞移關閉機制。
    • 使用 query / cquery 找出可能已加入新的依附元件的位置。
  2. TargetMetrics.targets_configured:代表建構中設定的目標和切面數量。迴歸代表在建構和掃遍設定的目標圖表方面工作更多。

    • 這通常是因為加入依附元件,且必須建構遞移封閉圖。
    • 使用 cquery 尋找可能已新增依附元件的位置。
  3. ActionSummary.actions_created:代表在建構中建立的動作,迴歸代表建構動作圖表的工作量較多。請注意,這也包含可能尚未執行且未使用的動作。

  4. ActionSummary.actions_executed:已執行的動作數量,直接迴歸代表在執行這些動作中的工作更多。

    • BEP 會寫入動作統計資料 ActionData,顯示最常執行的動作類型。根據預設,系統會收集前 20 個動作類型,但您可以傳入 --experimental_record_metrics_for_all_mnemonics,針對已執行的所有動作類型收集這類資料。
    • 這應該有助您瞭解所執行的哪些類型的動作 (額外)。
  5. BuildGraphSummary.outputArtifactCount:已執行動作建立的成果數量。

    • 如果執行的動作沒有增加,表示規則實作可能已變更。

這些指標都受到本機快取狀態的影響,因此您應確保從當中擷取這些指標的建構作業是「乾淨的建構作業」

我們發現,上述任何指標中的迴歸可能會伴隨實際時間、CPU 時間和記憶體用量進行迴歸。

使用本機資源

Bazel 會耗用本機電腦上的各種資源 (包括分析建構圖、驅動執行,以及執行本機動作),這可能會影響機器在執行建構作業時的效能 / 可用性,以及其他工作。

所用時間

或許最容易受到雜訊影響的指標 (從建構到建構的可能差異很大) 是時間,尤其是實際時間、CPU 時間和系統時間。您可以使用 bazel-bench 取得這些指標的基準,而當 --runs 數量足夠時,就能提高評估結果的統計顯著程度。

  • 實際時間是指經過的實際時間,

    • 如果「只有」實際時間迴歸,建議您收集 JSON 追蹤記錄設定檔,瞭解差異。否則,調查其他迴歸指標可能會影響實際時間,因此會更有效率。
  • CPU 作業時間是指 CPU 執行使用者程式碼花費的時間。

    • 如果 CPU 時間在兩個專案修訂版本中迴歸,建議您收集 Starlark CPU 設定檔。建議您也使用 --nobuild 將建構作業限制在分析階段,因為大部分耗用 CPU 的工作都是在分析階段完成。
  • 系統時間是指核心中 CPU 花費的時間。

    • 如果系統時間迴歸,當 Bazel 從檔案系統讀取檔案時,則主要與 I/O 有關。

整個系統負載剖析

只要使用 Bazel 6.0 中導入的 --experimental_collect_load_average_in_profiler 標記,JSON 追蹤分析器就會在叫用期間收集系統負載平均值。

包含系統負載平均值的設定檔

圖 1 包含系統負載平均值的設定檔。

Bazel 叫用期間的負載偏高,可能表示 Bazel 會為機器同時安排過多的本機動作。建議您調整 --local_cpu_resources--local_ram_resources,特別是在容器環境中 (至少在合併 #16512 之前)。

監控 Bazel 記憶體用量

如要取得 Bazel 的記憶體用量,有兩種主要來源:Bazel infoBEP

  • bazel info used-heap-size-after-gc:呼叫 System.gc() 後的記憶體用量,以位元組為單位。

    • Bazel bench 也會提供這項指標的基準。
    • 此外還有 peak-heap-sizemax-heap-sizeused-heap-sizecommitted-heap-size (請參閱說明文件),但不太相關。
  • BEPMemoryMetrics.peak_post_gc_heap_size:GC 後的尖峰 JVM 堆積大小大小 (需要設定 --memory_profile 來嘗試強制執行完整的 GC)。

記憶體用量發生迴歸通常是建構要求大小指標發生迴歸的結果,這通常是因為新增依附元件或規則實作有所變更。

如要更精細地分析 Bazel 的記憶體用量,建議您使用內建記憶體分析器建立規則。

永久工作站的記憶體分析

雖然永久性工作站有助於加快建構速度,特別是在解譯語言的情況下,其記憶體用量可能會產生問題。Bazel 會收集其工作站上的指標,特別是 WorkerMetrics.WorkerStats.worker_memory_in_kb 欄位會顯示 worker 的記憶體用量 (總和)。

JSON 追蹤記錄分析器也會傳入 --experimental_collect_system_network_usage 標記 (在 Bazel 6.0 中推出),藉此收集叫用期間的永久工作站記憶體用量。

包含工作站記憶體用量的設定檔

圖 2. 包括工作站記憶體用量的設定檔。

降低 --worker_max_instances 的值 (預設為 4) 可能有助於減少永久工作站使用的記憶體量。我們正在積極努力讓 Bazel 的資源管理員和排程器變得更聰明,之後就不必經常進行這類微調。

監控遠端建構作業的網路流量

在遠端執行中,Bazel 會下載因執行動作而建構的構件。因此,網路頻寬可能會影響建構的效能。

如果您要針對建構作業進行遠端執行作業,建議您使用 BEPNetworkMetrics.SystemNetworkStats proto (必須傳遞 --experimental_collect_system_network_usage),在叫用期間監控網路流量。

此外,JSON 追蹤記錄設定檔可讓您傳送 --experimental_collect_system_network_usage 標記 (在 Bazel 6.0 中推出),藉此在建構期間查看整個系統的網路用量。

含整個系統網路用量的設定檔

圖 3. 包含整個系統網路用量的設定檔。

使用遠端執行時,如果網路用量偏高但相對來說,可能表示網路是建構中的瓶頸。如果您尚未開始使用,請考慮透過傳遞 --remote_download_minimal,在沒有位元組的情況下啟用 Build。這麼做可以避免下載不必要的中繼成果,藉此加快建構速度。

另一個做法是設定本機磁碟快取以節省下載頻寬。