細分建構效能

回報問題 查看原始碼 Nightly · 8.0 · 7.4 · 7.3 · 7.2 · 7.1 · 7.0 · 7.1 · 7.0 · 6.5

Bazel 相當複雜,在建構期間會執行許多不同的動作,其中有些動作可能會影響建構效能。本頁面會嘗試將部分 Bazel 概念對應至其對建構效能的影響。雖然範例不多,但我們已提供一些示例,說明如何透過擷取指標來偵測版本效能問題,以及如何修正這些問題。我們希望您在調查建構作業效能回歸時,能運用這些概念。

清除與漸進式建構

全新建構作業會從頭開始建構所有內容,而增量建構作業則會重複使用部分已完成的工作。

建議您分別查看清除和增量版本,尤其是當您收集 / 匯總的評估指標取決於 Bazel 快取的狀態 (例如版本要求大小評估指標) 時。這兩種版本也代表兩種不同的使用者體驗。與從頭開始執行簡潔建構作業相比 (由於快取為冷快取,因此耗時較久),當開發人員重複執行程式碼時,增量建構作業會更頻繁地發生 (由於快取通常已為熱快取,因此通常會更快)。

您可以使用 BEP 中的 CumulativeMetrics.num_analyses 欄位來分類版本。如果是 num_analyses <= 1,則為清除式建構;否則,我們可以大致將其歸類為可能的增量式建構,因為使用者可能會切換至不同的標記或目標,導致有效的清除式建構。任何更嚴格的增量定義都可能以經驗法則的形式出現,例如查看載入的套件數量 (PackageMetrics.packages_loaded)。

將確定性的建構指標做為建構效能的代理值

由於某些指標 (例如 Bazel 的 CPU 時間或遠端叢集中的佇列時間) 具有非確定性,因此很難評估建構效能。因此,使用確定性指標做為 Bazel 完成的工作量代理值,可能會對其效能造成影響。

建構要求的大小可能會對建構效能造成重大影響。較大的版本可能需要更多工作來分析及建構建構圖表。隨著開發人員新增/建立更多依附元件,建構作業的複雜度也會隨之增加,因此建構作業的成本也會增加,這就是建構作業自然成長的情況。

我們可以將這個問題切割成不同的建構階段,並使用下列指標做為各階段完成工作的代理指標:

  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 的記憶體占用空間,建議您使用規則的內建記憶體分析器

對持久性 worker 進行記憶體分析

雖然持續性工作者可大幅加快建構作業 (尤其是針對解譯語言),但其記憶體用量可能會造成問題。Bazel 會收集工作站的指標,特別是 WorkerMetrics.WorkerStats.worker_memory_in_kb 欄位會指出工作站使用多少記憶體 (透過助憶法)。

JSON 追蹤記錄分析器也會在叫用期間傳入 --experimental_collect_system_network_usage 標記 (Bazel 6.0 的新功能),收集持續性工作站記憶體用量。

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

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

降低 --worker_max_instances 的值 (預設為 4) 可能有助於減少持續性工作站使用的記憶體量。我們正積極努力讓 Bazel 的資源管理工具和排程器更聰明,以便日後減少這類精細調整的需求。

監控遠端版本的網路流量

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

如果您使用遠端執行功能建構,建議您在呼叫期間使用 BEP 中的 NetworkMetrics.SystemNetworkStats 前置詞監控網路流量 (需要傳遞 --experimental_collect_system_network_usage)。

此外,您可以透過傳遞 --experimental_collect_system_network_usage 旗標 (Bazel 6.0 的新功能),透過 JSON 追蹤記錄設定檔,在整個建構過程中查看系統層級的網路用量。

包含系統全域網路用量的設定檔

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

使用遠端執行時,如果網路用量高且平穩,可能表示網路是建構的瓶頸;如果您尚未使用網路,請考慮透過傳遞 --remote_download_minimal 啟用不含位元的建構作業。這樣一來,系統就不會下載不必要的中繼構件,進而加快建構作業。

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