Trang này trình bày cách sử dụng worker liên tục, lợi ích, yêu cầu và ảnh hưởng của worker đến hộp cát.
Worker liên tục là một quá trình chạy trong thời gian dài do máy chủ Bazel bắt đầu,
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 là
chính công cụ đó. Để hưởng lợi từ trình thực thi liên tục, công cụ này phải
hỗ trợ thực hiện một trình tự biên dịch và trình bao bọc cần dịch
giữa API của công cụ và định dạng yêu cầu/phản hồi được mô tả bên dưới. Điều tương tự
worker có thể được gọi có và không có cờ --persistent_worker
trong phần tử
cùng một bản dựng và chịu trách nhiệm bắt đầu và trao đổi một cách thích hợp với
cũng như tắt trình thực thi khi thoát. Mỗi thực thể worker được chỉ định
(nhưng không bị can thiệp vào) một thư mục hoạt động riêng trong
<outputBase>/bazel-workers
.
Sử dụng trình thực thi liên tục là chiến lược thực thi giúp giảm mức hao tổn khi khởi động, cho phép biên dịch JIT nhiều hơn và cho phép lưu vào bộ nhớ đệm đối với ví dụ về 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 miền của chúng tôi.
Trình chạy liên tục được triển khai cho nhiều ngôn ngữ, bao gồm cả Java, Scala, Kotlin và các tính 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 trình thực thi.
Sử dụng trình thực thi liên tục
Bazel 0.27 trở lên
sử dụng worker liên tục theo mặc định khi thực thi các bản dựng, mặc dù làm việc từ xa
sẽ được ưu tiên thực thi. Đối với những thao tác không hỗ trợ trình thực thi liên tục,
Bazel quay lại bắt đầu một phiên bản công cụ cho mỗi hành động. Bạn có thể hiển thị rõ ràng
thiết lập bản dựng để sử dụng worker liên tục bằng cách đặt worker
chiến lược cho công cụ thích hợp
ghi nhớ. Cách hay nhất là trong ví dụ này, bạn nên chỉ định local
làm một
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 worker thay vì chiến lược cục bộ có thể giúp tăng cường biên dịch tốc độ đáng kể, tuỳ thuộc vào việc triển khai. Đối với Java, bản dựng có thể có từ 2 đến 4 nhanh hơn, đôi khi sẽ cao hơn khi biên dịch gia tăng. Biên dịch Bazel là nhanh gấp khoảng 2,5 lần so với người lao động. Để biết thêm chi tiết, hãy xem "Chọn số lượng nhân viên" .
Nếu bạn cũng có môi trường tạo bản dựng từ xa phù hợp với bản dựng cục bộ
bạn có thể sử dụng mô hình thử nghiệm
chiến lược động,
để thực thi một quá trình thực thi từ xa và thực thi worker. Để bật chế độ xem động
chiến lược, truyền
--experimental_spawn_scheduler
cờ. Chiến lược này tự động kích hoạt trình thực thi, 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 worker
Số lượng thực thể worker mặc định cho mỗi ghi nhớ là 4, nhưng bạn có thể điều chỉnh được
với
worker_max_instances
cờ. Có một sự đánh đổi giữa việc sử dụng hiệu quả CPU có sẵn và
số lượng truy cập biên dịch JIT và bộ nhớ đệm bạn nhận được. Với nhiều nhân viên hơn, nhiều hơn
mục tiêu sẽ trả chi phí khởi động khi chạy mã không phải JITt và kết hợp
bộ nhớ đệm. Nếu bạn có ít mục tiêu cần xây dựng, một nhân viên duy nhất có thể đưa ra
sự cân bằng tốt nhất giữa tốc độ biên dịch và mức sử dụng tài nguyên (ví dụ:
hãy xem vấn đề #8586.
Cờ worker_max_instances
đặt số lượng thực thể worker tối đa trên mỗi
ghi nhớ và gắn 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
khá nhiều bộ nhớ nếu bạn giữ giá trị mặc định. Đối với bản dựng tăng dần,
lợi ích của nhiều thực thể worker lại nhỏ hơn.
Biểu đồ này cho thấy thời gian biên dịch từ đầu cho Bazel (mục tiêu
//src:bazel
) trên máy trạm Linux siêu phân luồng 6 nhân Intel Xeon 3,5 GHz
có RAM 64 GB. Đối với mỗi cấu hình worker, 5 bản dựng sạch sẽ được chạy và
giá trị trung bình của bốn giá trị cuối cùng được lấy.
Hình 1. Biểu đồ mức độ cải thiện hiệu suất của các bản dựng sạch.
Đối với cấu hình này, 2 worker giúp quá trình biên dịch nhanh nhất, mặc dù chỉ ở mức 14% so với 1 nhân viên. Một worker là một lựa chọn phù hợp nếu bạn muốn sử dụng ít bộ nhớ hơn.
Việc biên dịch gia tăng thường mang lại nhiều lợi ích hơn nữa. Bản dựng sạch đang tương đối hiếm, nhưng việc thay đổi một tệp giữa các lần biên dịch là phổ biến, trong đặc biệt là trong quá trình phát triển dựa trên thử nghiệm. Ví dụ ở trên cũng có một số mã không phải là Java đóng gói các hành động có thể làm lu mờ thời gian biên dịch gia tăng.
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 một hằng số chuỗi nội bộ trong
AbstractContainerizingSandboxedSpawn.java
giúp tăng tốc độ gấp 3 lần (trung bình có 20 bản dựng tăng dần với một bản khởi động
đã huỷ):
Hình 2. Biểu đồ cải thiện hiệu suất của các 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 độ của yếu tố 6 là được đo lường trong tình huống ở trên khi một hằng số thường dùng được thay đổi.
Sửa đổi trình thực thi liên tục
Bạn có thể chuyển
--worker_extra_flag
để chỉ định cờ khởi động cho các trình thực thi, được khoá bằng ghi nhớ. Ví dụ:
việc truyền --worker_extra_flag=javac=--debug
sẽ chỉ bật tính năng gỡ lỗi cho Javac.
Bạn chỉ có thể đặt một cờ worker cho mỗi lần sử dụng cờ này và chỉ cho một ghi nhớ.
Worker không chỉ được tạo riêng cho từng ghi nhớ mà còn cho
các biến thể khác nhau của cờ khởi động. Từng cách kết hợp giữa việc ghi nhớ và khởi động
cờ được kết hợp thành một WorkerKey
và đối với mỗi WorkerKey
tối đa
Bạn có thể tạo worker_max_instances
worker. Xem phần tiếp theo để biết cách
cấu hình thao tác cũng có thể chỉ định cờ thiết lập.
Bạn có thể sử dụng
--high_priority_workers
cờ để chỉ định một bộ nhớ sẽ được chạy ưu tiên so với mức độ ưu tiên thông thường
ghi nhớ. Điều này có thể giúp ưu tiên các hành động luôn ở giai đoạn quan trọng
đường dẫn. Nếu có từ 2 trình thực thi có mức độ ưu tiên cao trở lên thực thi yêu cầu, thì tất cả
không cho các worker khác chạy. Cờ này có thể được sử dụng nhiều lần.
Vượt qua
--worker_sandboxing
cờ làm cho mỗi yêu cầu worker sử dụng một thư mục hộp cát riêng cho tất cả
đầu vào. Việc thiết lập hộp cát sẽ mất thêm thời gian,
đặc biệt là trên macOS, nhưng lại đảm bảo độ chính xác cao hơn.
Chiến lược phát hành đĩa đơn
--worker_quit_after_build
cờ hiệu chủ yếu hữu ích cho việc gỡ lỗi và phân tích tài nguyên. Cờ này buộc tất cả các worker
thoát sau khi hoàn tất quá trình tạo bản dựng. Bạn cũng có thể chuyển
--worker_verbose
đến
có được kết quả tốt hơn về những gì 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ác hoạt động triển khai trình thực thi
chi tiết hơn.
Worker lưu trữ nhật ký trong thư mục <outputBase>/bazel-workers
, đối với
ví dụ
/tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log
Tên tệp bao gồm mã trình thực thi và tên ghi nhớ. Vì có thể có thêm
nhiều hơn một WorkerKey
cho mỗi ghi nhớ, bạn có thể thấy nhiều hơn worker_max_instances
tệp nhật ký cho một ghi nhớ nhất định.
Đối với các bản dựng Android, hãy xem chi tiết tại Trang Hiệu suất bản dựng Android.
Triển khai trình thực thi liên tục
Xem trang tạo trình thực thi liên tục để biết thêm thông tin thông tin về cách tạo ra một worker.
Ví dụ dưới đây cho thấy cấu hình Starlark cho một worker 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, lần đầu tiên sử dụng 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
. Một yêu cầu
để biên dịch, Foo.java
sẽ có dạng như sau:
LƯU Ý: Mặc dù thông số kỹ thuật vùng đệm giao thức sử dụng "ví dụ về trường hợp rắn" (request_id
),
giao thức JSON sử dụng "kiểu lạc đà" (requestId
). Trong tài liệu này, chúng tôi sẽ sử dụng
kiểu viết lạc đà trong các ví dụ về JSON, nhưng viết hoa kiểu rắn khi nói về trường
bất kể giao thức là gì.
{
"arguments": [ "-g", "-source", "1.5", "Foo.java" ]
"inputs": [
{ "path": "symlinkfarm/input1", "digest": "d49a..." },
{ "path": "symlinkfarm/input2", "digest": "093d..." },
],
}
Worker này nhận được dữ liệu này vào 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 đó, worker này thực hiện hành động
và gửi WorkResponse
có định dạng JSON tới Bazel trên stdout. Sau đó là Bazel
phân tích cú pháp phản hồi này và chuyển đổi phản hồi này thành proto WorkResponse
theo cách thủ công. Người nhận
giao tiếp với worker được liên kết bằng protobuf được mã hoá nhị phân thay vì
JSON, requires-worker-protocol
sẽ được thiết lập 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 yêu cầu thực thi,
Bazel sẽ mặc định cho phép giao tiếp của worker sử dụng protobuf.
Bazel lấy WorkerKey
từ ghi nhớ và cờ chung, vì vậy nếu đây
cấu hình cho phép thay đổi tham số max_mem
, thì một worker riêng sẽ
được tạo cho mỗi giá trị đã dùng. Điều này có thể dẫn đến mức tiêu thụ bộ nhớ quá mức nếu
sử dụng quá nhiều biến thể.
Hiện tại, mỗi worker chỉ có thể xử lý một yêu cầu tại một thời điểm. Chương trình thử nghiệm Tính năng multix worker cho phép sử dụng nhiều các luồng, nếu công cụ cơ bản là đa luồng và trình bao bọc được thiết lập để hiểu điều này.
Trong kho lưu trữ GitHub này, bạn có thể xem ví dụ về trình bao bọc trình thực thi được viết bằng Java cũng như Python. Nếu bạn đang xử lý JavaScript hoặc TypeScript, @bazel/worker package và ví dụ về trình thực thinodejs có thể hữu ích.
Worker ảnh hưởng như thế nào đến hộp cát?
Việc sử dụng chiến lược worker
theo mặc định sẽ không chạy hành động trong một
sandbox, tương tự như chiến lược local
. Bạn có thể đặt
--worker_sandboxing
gắn cờ để chạy tất cả worker bên trong hộp cát, đảm bảo mỗi
việc thực thi công cụ chỉ thấy các tệp đầu vào đáng lẽ phải có. Công cụ
vẫn có thể làm rò rỉ thông tin giữa các yêu cầu trong nội bộ, chẳng hạn như thông qua
bộ nhớ đệm. Đang sử dụng chiến lược dynamic
yêu cầu worker phải tạo hộp cát.
Để cho phép sử dụng đúng bộ nhớ đệm của trình biên dịch với worker, chuỗi đại diện sẽ được truyền với từng tệp đầu vào. Do đó, 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ó vẫn hợp lệ mà không phải đọc tệp.
Ngay cả khi sử dụng chuỗi đại diện đầu vào để bảo vệ khỏi hoạt động lưu vào bộ nhớ đệm không mong muốn, tính năng này được đặt trong hộp cát worker cung cấp hộp cát ít nghiêm ngặt hơn so với hộp cát thuần tuý vì công cụ này có thể giữ lại trạng thái nội bộ khác đã bị ảnh hưởng bởi những yêu cầu trước đó.
Multiplex worker chỉ có thể được tạo hộp cát nếu hoạt động triển khai worker hỗ trợ nó,
và hộp cát này phải được bật riêng với
Cờ --experimental_worker_multiplex_sandboxing
. Xem thêm 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ề trình thực thi liên tục, hãy xem:
- Bài đăng ban đầu trên blog về worker cố định
- Nội dung mô tả về việc 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à Persistent Workers w/ Asana {: .external}
- Giải thích về các chiến lược Bazel {: .external}
- Thảo luận về chiến lược nhân viên thông tin trên danh sách gửi thư thảo luận về bazel {: .external}