永久工作站

本頁面說明如何使用永久工作站、優點、需求,以及工作站對沙箱機制的影響。

永久的工作站是由 Bazel 伺服器啟動的長時間執行程序,可在實際工具 (通常是編譯器) 中運作,也可以當做工具本身。為了讓永久工作站受益,這項工具必須支援一系列編譯,包裝函式需要在工具的 API 與下方所述的要求/回應格式之間進行翻譯。同一個工作站可能會被呼叫,而同一版本沒有 --persistent_worker 標記,而且會負責妥善啟動和交談工具,並在關閉時關閉工作站。系統會在 <outputBase>/bazel-workers 底下為各個工作站執行個體指派 (但不是根層級) 的工作目錄。

使用永久工作站是一種執行策略,可減少啟動負擔、允許更多 JIT 編譯,以及對執行動作中的抽象語法樹狀結構進行快取。這項策略會將多個要求傳送至長時間執行的程序,藉此實現這些改善。

持續處理的工作站支援多種語言,包括 Java、ScalaKotlin 等等。

使用 NodeJS 執行階段的程式可以使用 @bazel/worker 輔助程式庫來執行工作站通訊協定。

使用永久工作站

根據預設,執行 Bazel 的 Bazel 0.27 以上版本會使用永久工作站。對於不支援永久工作站的工作,Bazel 會恢復為每項動作啟動工具執行個體。您可以設定適用工具的 worker 策略,將建構作業設定為明確使用永久工作站。此示例的最佳做法包括指定 local 作為 worker 策略的 回復:

bazel build //my:target --strategy=Javac=worker,local

使用工作站策略 (而非本機策略) 可大幅改善編譯速度 (視實作情況而定)。在 Java 中,建構作業的速度可能快 2 到 4 倍,有時甚至有助於進行編譯。執行 Bazel 的速度比工作站快 2.5 倍。詳情請參閱選擇工作站數量一節。

如果您的遠端建構環境也與您的本機建構環境相符,則可以使用實驗性動態策略,用於進行遠端執行作業和工作站執行作業。如要啟用動態策略,請傳送 --experimental_spawn_Scheduler 標記。這個策略會自動啟用工作站,因此您不需要指定 worker 策略,但您還是可以使用 localsandboxed 做為備用選項。

選擇工作站數量

每個 memonic 工作站執行個體的預設數量為 4 個,但您可以使用 worker_max_instances 標記進行調整。妥善利用可用的 CPU,以及 JIT 編譯和取得的快取命中量之間存在取捨。工作量增加時,更多目標就會產生初次執行成本,且執行非 JITted 程式碼並點擊冷快取。如果您要建立的目標數量不多,單一工作站可能會在編譯速度和資源用量之間做出最佳取捨 (例如問題 #8586)。worker_max_instances 旗標會設定每個助力器的工作站執行個體數量上限和標記集 (請見下方說明),因此在混合式系統中,如果您保留預設值,則可使用大量記憶體。使用漸進式建構作業時,使用多個工作站執行個體的優點比較小。

此圖表顯示 Bazel (目標 //src:bazel) 在具有 64 GB RAM 的 6 核心超執行緒 Intel Xeon 3.5 GHz Linux 工作站上,從初期的編譯編譯時間。針對每個工作站設定,系統會執行五個乾淨的建構作業,然後擷取最近四個資料的平均值。

清除乾淨版本的效能提升圖表

圖 1. 清除乾淨版本的效能提升圖表。

以這項設定來說,有兩個工作站提供最快的編譯,但相較於單一工作站,只有 14% 的改進。如果您想減少記憶體用量,建議您使用一個工作站。

增加的編譯通常會有比較好的好處。乾淨的建構作業相對罕見,但在編譯版開發中會變更單一檔案,特別是在測試驅動的開發作業中。上述範例也包含了對其執行的非 Java 封裝動作,而這可能會重疊於編譯的編譯時間。

AbstractContainerizingSandboxedSpawn.java 中變更內部字串常數之後,僅重新編譯 Java 來源 (//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar) 可加快 3 倍的加速 (平均 20 個增量建構作業,且捨棄一個暖身建構)。

漸進式建構作業的效能改善圖表

圖 2:漸進式建構作業的效能改善圖表。

且變更速度取決於變更項目。在上述情況下,如有常用的常數改變,上述因素就會評估係數 6 的加速。

修改永久工作站

您可以傳遞 --worker_extra_flag 標記,將啟動標記標記為工作站,並以 mnemonic 為金鑰。例如,傳送 --worker_extra_flag=javac=--debug 只會開啟 Javac 的偵錯功能。使用此標記時只能設定一個工作站標記,而且只能有一個助力標記。 工作站並非單獨為每一個墓地分別建立,也包含啟動標記中的變化。將多餘的標記和啟動標記合併為一個 WorkerKey,每個 WorkerKey 最多可以建立 worker_max_instances 個工作站。請參閱下一節,瞭解動作設定如何指定設定標記。

您可以使用 --high_priority_workers 旗標指定應優先使用一般優先順序的鈴聲。這有助於優先處理在關鍵路徑中的動作。如果有兩個以上的優先順序高的工作站執行要求,則所有其他工作站均會遭到封鎖。此旗標可以多次使用。

傳送 --worker_sandboxing 標記會讓每個工作站要求對其所有輸入內容使用獨立的沙箱目錄。設定 sandbox 需要額外的時間 (尤其是在 macOS 上),但給予更準確的修正保證。

--worker_quit_after_build 標記主要用於偵錯和剖析。這個標記會強制所有工作站在建構作業完成後退出。也可以傳遞 --worker_verbose,藉此取得更多工作站所執行的輸出內容。這個旗標會反映在 WorkRequestverbosity 欄位,讓工作站實作更進一步。

工作站將記錄檔儲存在 <outputBase>/bazel-workers 目錄中,例如 /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log。檔案名稱包含工作站 ID 和 mnemonic。由於每個 Memonic 可以有多個 WorkerKey,因此您可能會看見超過一個 worker_max_instances 的記錄檔在指定的助力下。

如果是 Android 版本,請參閱 Android Build Performance 頁面中的詳細資料。

實作永久性工作站

如要瞭解如何建立工作站,請參閱建立永久工作站頁面。

以下是使用 JSON 工作站的 Starlark 設定範例:

args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
ctx.actions.write(
    output = args_file,
    content = "\n".join(["-g", "-source", "1.5"] + ctx.files.srcs),
)
ctx.actions.run(
    mnemonic = "SomeCompiler",
    executable = "bin/some_compiler_wrapper",
    inputs = inputs,
    outputs = outputs,
    arguments = [ "-max_mem=4G",  "@%s" % args_file.path],
    execution_requirements = {
        "supports-workers" : "1", "requires-worker-protocol" : "json" }
)

透過這個定義,第一次使用這個動作將首先執行指令列 /bin/some_compiler -max_mem=4G --persistent_worker。編譯 Foo.java 的要求如下所示:

arguments: [ "-g", "-source", "1.5", "Foo.java" ]
inputs: [
  {path: "symlinkfarm/input1" digest: "d49a..." },
  {path: "symlinkfarm/input2", digest: "093d..."},
]

工作站會在 stdin 以新的以換行符號分隔的 JSON 格式接收此訊息 (因為 requires-worker-protocol 已設為 JSON)。接著,工作站會執行該動作,並將 JSON 格式的 WorkResponse 傳送至 Bazel 的 stdout。Bazel 會剖析此回應並手動轉換為 WorkResponse proto。如要透過二元編碼的 protobuf (而非 JSON) 與相關聯的工作站進行通訊,requires-worker-protocol 會設為 proto,如下所示:

  execution_requirements = {
    "supports-workers" : "1" ,
    "requires-worker-protocol" : "proto"
  }

如果您未在執行要求中加入 requires-worker-protocol,Bazel 會根據預設將工作站通訊設定為使用 protobuf。

Bazel 會從 Mememonic 和共用旗標取得 WorkerKey,因此,如果這項設定允許變更 max_mem 參數,系統會為每個使用的值產生獨立的工作站。如果使用太多變化,可能會導致記憶體用量過大。

每個工作站目前一次只能處理一個要求。實驗性的多工工作站功能允許使用多個執行緒,如果基礎工具是多執行緒技術,且包裝函式設定為瞭解這種設定即可。

這個 GitHub 存放區中,您可以查看以 Java 和 Python 編寫的工作站包裝函式範例。如果您使用的是 JavaScript 或 TypeScript,@bazel/worker 套件nodejs Worker 範例或許有所幫助。

工作站對沙箱機制有何影響?

根據預設,使用 worker 策略並不會在沙箱中執行動作,類似 local 策略。您可以設定 --worker_sandboxing 標記,在沙箱中執行所有工作站,確保工具的每項執行作業都只會看到它原本應有的輸入檔案。這項工具還是可能會在內部要求之間洩漏資訊,例如透過快取。使用 dynamic 策略要求工作站必須採用沙箱機制

為使工作站正確使用編譯器快取,系統會隨每個輸入檔案一起傳送摘要。因此,編譯器或包裝函式可以檢查輸入是否仍然有效,而不需讀取檔案。

即使使用輸入摘要來防止不必要的快取,沙箱工作人員的沙箱機制也會比純沙箱來得少,因為這項工具可能會保留之前受到先前要求影響的其他內部狀態。

只有在工作站實作支援多重工作站時,才能以沙箱模式為多個工作站執行沙箱作業,且該沙箱作業必須使用 --experimental_worker_multiplex_sandboxing 標記分開啟用。詳情請參閱設計文件)。

延伸閱讀

如要進一步瞭解永久性工作站,請參閱: