Người làm việc lâu dài

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.

Biểu đồ cải thiện hiệu suất cho bản dựng sạch

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ỏ):

Biểu đồ cải thiện hiệu suất cho bản dựng tăng dần

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 packagenodejsWorker 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: