Trang này cho biết cách sử dụng nhân viên cố định, các lợi ích, yêu cầu và cách nhân viên ảnh hưởng đến hộp cát.
Trình chạy cố định là một quá trình chạy trong thời gian dài do máy chủ Bazel bắt đầu. Chức năng này hoạt động như một trình bao bọc xung quanh công cụ thực tế (thường là trình biên dịch), hoặc chính là công cụ đó. Để hưởng lợi từ những nhân viên cố định, công cụ phải hỗ trợ việc thực hiện một trình tự tổng hợp và trình bao bọc cần phải dịch giữa API của công cụ và định dạng yêu cầu/phản hồi như được mô tả bên dưới. Cùng một trình chạy công việc
có thể được gọi có và không có cờ --persistent_worker
trong cùng một bản dựng, đồng thời chịu trách nhiệm bắt đầu và trò chuyện với công cụ đó một cách thích hợp, cũng như tắt cả nhân viên trong khi thoát. Mỗi thực thể nhân viên được chỉ định
(nhưng không được tập trung vào) một thư mục làm việc riêng biệt trong
<outputBase>/bazel-workers
.
Việc sử dụng trình chạy cố định là một chiến lược thực thi giúp giảm chi phí khởi động, cho phép tổng hợp nhiều JIT hơn và cho phép lưu vào bộ nhớ đệm của ví dụ về các cây cú pháp trừu tượng trong quá trình thực thi hành động. Chiến lược này đạt được những cải tiến này bằng cách gửi nhiều yêu cầu đến một quy trình chạy trong thời gian dài.
Các trình chạy cố định được triển khai cho nhiều ngôn ngữ, bao gồm cả Java, Scala, Kotlin và các ngôn ngữ khác.
Các chương trình sử dụng thời gian chạy NodeJS có thể sử dụng thư viện trợ giúp @bazel/worker để triển khai giao thức Worker.
Sử dụng nhân viên cố định
Bazel 0.27 trở lên sử dụng nhân viên cố định theo mặc định khi thực thi các bản dựng, mặc dù việc thực thi từ xa
được ưu tiên. Đối với những hành động không hỗ trợ nhân viên cố định,
Bazel sẽ quay lại để bắt đầu sử dụng một công cụ cho mỗi hành động. Bạn có thể thiết lập
bản dựng của mình một cách rõ ràng để sử dụng nhân viên cố định bằng cách đặt worker
chiến lược cho công cụ áp dụng
công cụ hiện hành. Phương pháp hay nhất là ví dụ này bao gồm việc chỉ định local
làm
dự phòng cho chiến lược worker
:
bazel build //my:target --strategy=Javac=worker,local
Việc sử dụng chiến lược nhân viên thay vì chiến lược địa phương có thể tăng đáng kể tốc độ biên dịch, tùy thuộc vào cách triển khai. Đối với Java, các bản dựng có thể nhanh hơn 2–4 lần, đôi khi nhiều hơn cho việc tổng hợp tăng dần. Khi biên dịch Bazel, tốc độ của nhân viên nhanh hơn khoảng 2,5 lần. Để biết thêm thông tin chi tiết, hãy xem phần "Chọn số lượng nhân viên"
Nếu cũng có môi trường xây dựng từ xa phù hợp với môi trường xây dựng địa phương, thì bạn có thể sử dụng chiến lược
linh hoạt thử nghiệm để thực thi từ xa việc thực thi và thực thi của nhân viên. Để bật chiến lược động, hãy chuyển cờ
-- Experiment_spawn_ Scheduler. Chiến lược này tự động cho phép nhân viên, vì vậy, bạn không cần
chỉ định chiến lược worker
, nhưng bạn vẫn có thể sử dụng local
hoặc sandboxed
làm
dự phòng.
Chọn số lượng nhân viên
Số thực thể nhân viên mặc định trên mỗi mnemonic là 4, nhưng có thể điều chỉnh
bằng cờ
worker_max_instances
. Có sự đánh đổi giữa việc tận dụng tốt CPU có sẵn và khối lượng biên dịch JIT và số lượt truy cập bộ nhớ đệm bạn nhận được. Với nhiều nhân viên hơn, nhiều mục tiêu hơn sẽ trả chi phí khởi động việc chạy mã không phải JIT và đánh dấu bộ nhớ đệm lạnh. Nếu bạn chỉ có một số ít mục tiêu để tạo, một nhân viên có thể đánh đổi
tâm lý tốt nhất giữa tốc độ biên dịch và mức sử dụng tài nguyên (ví dụ:
xem vấn đề #8586).
Cờ worker_max_instances
đặt số lượng thực thể nhân viên tối đa trên mỗi
bộ nhớ cờ và nhóm cờ (xem bên dưới), vì vậy, trong một hệ thống hỗn hợp, bạn có thể sử dụng
nhiều bộ nhớ nếu vẫn giữ giá trị mặc định. Đối với các bản dựng tăng dần,
lợi ích của nhiều phiên bản Worker thậm chí còn nhỏ hơn.
Biểu đồ này hiển thị thời gian tổng hợp cào từ Bazel (mục tiêu
//src:bazel
) trên máy trạm Linux Xeon 3,5 GHz siêu phân luồng
với 64 GB RAM. Đối với mỗi cấu hình của nhân viên, 5 bản dựng sạch được chạy và trung bình của 4 bản dựng cuối cùng được lấy.
Hình 1. Biểu đồ cải thiện hiệu suất cho bản dựng sạch.
Đối với cấu hình này, hai nhân viên cung cấp trình biên dịch nhanh nhất, mặc dù chỉ cải thiện 14% so với một nhân viên. Một nhân viên là một lựa chọn tốt nếu bạn muốn sử dụng ít bộ nhớ hơn.
Lợi ích của việc biên dịch tăng dần thường mang lại nhiều lợi ích hơn nữa. Các bản dựng rõ ràng là tương đối hiếm, nhưng việc thay đổi một tệp giữa các trình biên dịch là rất phổ biến, đặc biệt là trong quá trình phát triển theo hướng thử nghiệm. Ví dụ trên cũng có một số thao tác đóng gói không phải là Java có thể làm thay đổi thời gian biên dịch tăng dần.
Chỉ biên dịch lại các nguồn Java
(//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar
)
sau khi thay đổi hằng số chuỗi nội bộ trong
abstractContainerizationSandboxedSpawn.java
giúp tăng tốc độ tăng gấp 3 lần (trung bình 20 bản dựng tăng dần với một bản dựng khởi động
bị loại bỏ):
Hình 2. Biểu đồ cải thiện hiệu suất cho bản dựng tăng dần.
Việc tăng tốc độ phụ thuộc vào thay đổi đang được thực hiện. Tốc độ tăng của hệ số 6 được đo trong trường hợp trên khi một hằng số thường dùng bị thay đổi.
Sửa đổi nhân viên cố định
Bạn có thể chuyển cờ
--worker_extra_flag
để chỉ định cờ khởi động cho nhân viên, được khóa bởi mnemonic. Ví dụ:
Việc chuyển --worker_extra_flag=javac=--debug
chỉ bật tính năng gỡ lỗi cho Javac.
Bạn chỉ có thể đặt một cờ của nhân viên trên mỗi lần dùng cờ này, và chỉ dùng được cho một âm thanh.
Trình chạy không chỉ được tạo riêng cho từng kỹ thuật ghi âm mà còn cho các biến thể trong cờ khởi động của họ. Mỗi tổ hợp cờ nhớ và khởi động
được kết hợp thành một WorkerKey
và đối với mỗi WorkerKey
, có thể tạo tối đa
worker_max_instances
nhân viên. Hãy xem phần tiếp theo để biết
cách cấu hình hành động cũng có thể chỉ định cờ thiết lập.
Bạn có thể sử dụng cờ
--high_priority_workers
để chỉ định một mnemonic nào đó sẽ chạy ưu tiên cho các tính năng ghi âm
có mức độ ưu tiên bình thường. Điều này có thể giúp ưu tiên những hành động luôn nằm trong đường dẫn
quan trọng. Nếu có hai hoặc nhiều nhân viên ưu tiên thực hiện yêu cầu, tất cả
các nhân viên khác sẽ bị ngăn không cho chạy. Bạn có thể sử dụng cờ này nhiều lần.
Việc chuyển cờ
--worker_sandboxing
giúp mỗi yêu cầu của nhân viên sử dụng một thư mục hộp cát riêng cho tất cả hoạt động đầu vào của
nhân viên. Việc thiết lập hộp cát sẽ mất nhiều thời gian hơn, đặc biệt là trên macOS, nhưng đảm bảo độ chính xác cao hơn.
Cờ
--worker_quit_after_build
chủ yếu hữu ích cho việc gỡ lỗi và hồ sơ. Cờ này buộc tất cả nhân viên phải thoát khỏi sau khi xây dựng xong. Bạn cũng có thể chuyển--worker_verbose
để
nhận được nhiều kết quả hơn về những việc mà nhân viên đang làm. Cờ này được phản ánh trong trường
verbosity
trong WorkRequest
, cho phép cấu hình triển khai Worker cũng
chi tiết hơn.
Worker sẽ lưu nhật ký của họ trong thư mục <outputBase>/bazel-workers
, ví dụ:
/tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log
.
Tên tệp bao gồm id Worker và mnemonic. Vì có thể có nhiều hơn WorkerKey
tệp trên mỗi mnemonic nên bạn có thể thấy nhiều hơn worker_max_instances
tệp nhật ký cho một mnemonic nhất định.
Đối với các bản dựng Android, hãy xem thông tin chi tiết tại trang Hiệu suất bản dựng Android.
Triển khai nhân viên cố định
Xem trang tạo nhân viên cố định để biết thông tin về cách tạo nhân viên.
Ví dụ này hiển thị cấu hình Starlark cho một trình chạy dịch vụ sử dụng JSON:
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" }
)
Với định nghĩa này, việc sử dụng đầu tiên của hành động này sẽ bắt đầu bằng việc thực thi
dòng lệnh /bin/some_compiler -max_mem=4G --persistent_worker
. Yêu cầu biên dịch Foo.java
sẽ có dạng như sau:
arguments: [ "-g", "-source", "1.5", "Foo.java" ]
inputs: [
{path: "symlinkfarm/input1" digest: "d49a..." },
{path: "symlinkfarm/input2", digest: "093d..."},
]
Nhân viên này nhận được dữ liệu này trên stdin
ở định dạng JSON được phân tách bằng dòng mới (vì
requires-worker-protocol
được đặt thành JSON). Sau đó, nhân viên này thực hiện hành động và gửi một WorkResponse
có định dạng JSON đến Bazel trên stdout của mình. Sau đó, Bazel
phân tích cú pháp phản hồi này và chuyển đổi phản hồi đó thành một giao thức WorkResponse
theo cách thủ công. Để
giao tiếp với nhân viên liên kết bằng cách sử dụng giao thức mã hóa nhị phân thay vì
JSON, requires-worker-protocol
sẽ được đặt thành proto
, như sau:
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
Nếu bạn không đưa requires-worker-protocol
vào các yêu cầu thực thi,
Bazel sẽ mặc định giao tiếp với nhân viên để sử dụng Protobuf.
Bazel lấy WorkerKey
từ thẻ ghi âm và cờ được chia sẻ, vì vậy, nếu cấu hình này
cho phép thay đổi thông số max_mem
, thì một trình chạy riêng
sẽ xuất hiện cho từng giá trị được sử dụng. Điều này có thể dẫn đến việc tiêu thụ bộ nhớ quá mức nếu
quá nhiều biến thể được sử dụng.
Hiện tại, mỗi nhân viên chỉ có thể xử lý một yêu cầu tại một thời điểm. Tính năng nhiều nhân viên thử nghiệm cho phép sử dụng nhiều luồng, nếu công cụ cơ bản hoạt động theo nhiều luồng và trình bao bọc được thiết lập để hiểu được điều này.
Trong kho lưu trữ GitHub này, bạn có thể thấy các trình bao bọc trình chạy mẫu được viết bằng Java cũng như trong Python. Nếu bạn đang làm việc trong JavaScript hoặc TypeScript, thì @bazel/worker package và nodejsWorker example có thể hữu ích.
Nhân viên ảnh hưởng như thế nào đến hộp cát?
Theo mặc định, việc sử dụng chiến lược worker
không chạy hành động trong
hộp cát, tương tự như chiến lược local
. Bạn có thể đặt cờ --worker_sandboxing
để chạy tất cả nhân viên bên trong hộp cát, đảm bảo mỗi
thực thi của công cụ chỉ thấy các tệp đầu vào mà công cụ đó yêu cầu phải có. Công cụ này
có thể vẫn làm rò rỉ thông tin giữa các yêu cầu nội bộ, chẳng hạn như thông qua
bộ nhớ đệm. Sử dụng chiến lược dynamic
yêu cầu nhân viên phải nằm trong hộp cát.
Để cho phép sử dụng đúng bộ nhớ đệm của trình biên dịch với trình chạy dịch vụ, thông báo sẽ được chuyển đi kèm với mỗi tệp đầu vào. Do vậy, trình biên dịch hoặc trình bao bọc có thể kiểm tra xem dữ liệu đầu vào có hợp lệ hay không mà không cần phải đọc tệp.
Ngay cả khi sử dụng thông báo đầu vào để bảo vệ khỏi việc lưu vào bộ nhớ đệm không mong muốn, các nhân viên hộp cát cung cấp hộp cát ít nghiêm ngặt hơn so với hộp cát nguyên bản, vì công cụ này có thể giữ nguyên trạng thái nội bộ khác đã bị ảnh hưởng bởi các yêu cầu trước đó.
Hộp cát cho nhiều nhân viên chỉ có thể chạy trong môi trường hộp cát nếu trình triển khai này hỗ trợ hộp cát này,
và hộp cát này phải được bật riêng bằng cờ
--experimental_worker_multiplex_sandboxing
. Xem thêm thông tin chi tiết trong
tài liệu thiết kế.)
Tài liệu đọc thêm
Để biết thêm thông tin về nhân viên cố định, hãy xem:
- Bài đăng ban đầu trên blog của nhân viên cố định
- Mô tả cách triển khai Haskell {: .external}
- Bài đăng trên blog của Mike Morearty {: .external}
- Phát triển giao diện người dùng bằng Bazel: Angular/TypeScript và Constant Works w/ Asana {: .external}
- Giải thích về các chiến lược củaBazel {: .external}
- Thảo luận chiến lược nhân viên thông tin về danh sách gửi thư thảo luận {: .external}