細分建構效能

回報問題 查看來源 Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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 基準也會提供這項指標的基準。
    • 此外,還有 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 欄位,可顯示工作站使用的記憶體量 (依助記符)。

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

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

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

降低 --worker_max_instances 值 (預設為 4) 可能有助於減少持續性工作站使用的記憶體量。我們正積極改良 Bazel 的資源管理工具和排程器,希望日後能減少這類微調作業。

監控遠端建構的網路流量

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

如果您在建構時使用遠端執行,不妨考慮在叫用期間使用 BEPNetworkMetrics.SystemNetworkStats proto 監控網路流量 (需要傳遞 --experimental_collect_system_network_usage)。

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

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

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

使用遠端執行時,如果網路用量偏高但相當平穩,可能表示網路是建構作業的瓶頸。如果尚未啟用「建構但不傳送位元組」功能,請考慮傳遞 --remote_download_minimal 來啟用這項功能。這樣一來,系統就不會下載不必要的中間構件,建構速度也會加快。

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