細分建構效能

回報問題 查看原始碼

Bazel 在建構過程中會進行許多複雜的工作,而且其中有許多不同,可能會影響建構效能。本頁面會嘗試將部分 Bazel 概念對應至這些建構對效能的影響。在本文中,我們並未參考一些範例,說明如何透過擷取指標來偵測建構效能問題,以及如何加以修正。因此,我們希望您在建構建構效能迴歸時可運用這些概念。

清理與漸進式建構

簡潔的建構作業是從頭開始建構的建構作業,漸進式建構作業會重複使用部分已完成的工作。

建議您分別查看簡潔和漸進式建構作業,特別是當您收集 / 匯總依附 Bazel 快取狀態的指標 (例如建構要求大小指標) 時,這兩個指標代表兩個不同的使用者體驗。與從冷快取開始啟動清理作業相比,從頭開始啟動清理作業需要較長的時間,因為開發人員在處理程式碼疊代時,執行頻率會較快 (通常快取速度更快,這是因為快取通常較暖)。

您可以使用 BEP 中的 CumulativeMetrics.num_analyses 欄位將建構作業分類。如果 num_analyses <= 1 是簡潔的建構;否則,我們大致可以將其歸類為漸進式建構,使用者可以切換至不同標記或不同的目標,以有效率地執行建構作業。任何更嚴謹的增量定義可能必須採用經驗法則的形式,例如查看已載入的套件數量 (PackageMetrics.packages_loaded)。

確定性建構指標做為建構效能的 Proxy

由於某些指標具有不確定性特性 (例如 Bazel 的遠端 CPU 時間或遠端叢集的佇列時間),因此難以測量建構效能。因此,使用確定性指標做為 Bazel 處理的量量,這對於 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 本位也提供這項指標的基準。
    • 此外,還有 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)。

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

包含整個系統網路的設定檔

圖 3:包含整個系統網路的設定檔。

使用遠端執行時,網路用量偏高但平坦時,可能表示網路是建構中的瓶頸;如果尚未使用,請考慮通過 --remote_download_minimal 來啟用 Build 而不使用位元組。這樣可避免下載不必要的中繼成果,進而加快建構速度。

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