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

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Báo cáo vấn đề Xem nguồn

Trang này trình bày cách sử dụng trình chạy liên tục, lợi ích, yêu cầu và cách trình chạy ảnh hưởng đến hộp cát.

Trình chạy ổn định là một quá trình lâu dài do máy chủ Bazel bắt đầu, có chức năng như một trình bao bọc xung quanh công cụ thực tế (thường là một trình biên dịch) hoặc chính là công cụ đó. Để hưởng lợi từ trình chạy liên tục, công cụ phải hỗ trợ thực hiện 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ụ cũng như định dạng yêu cầu/phản hồi như mô tả bên dưới. Trình chạy này có thể được gọi cùng với 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 trình chạy 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 biệt trong <outputBase>/bazel-workers.

Sử dụng worker ổn định là một chiến lược thực thi giúp giảm chi phí 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 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 điểm cải tiến này bằng cách gửi nhiều yêu cầu đến một quy trình dài hạn.

Trình chạy ổn định được triển khai cho nhiều ngôn ngữ, bao gồm Java, Scala, Kotlin, v.v.

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 chạy.

Sử dụng các worker lâu dài

Theo mặc định, Bazel 0.27 trở lên sử dụng worker liên tục khi thực thi các bản dựng, mặc dù quá trình thực thi từ xa sẽ được ưu tiên hơn. Đối với các thao tác không hỗ trợ trình chạy cố định, Bazel sẽ quay lại sử dụng phiên bản công cụ cho mỗi thao tác. Bạn có thể thiết lập rõ ràng bản dựng để sử dụng các worker liên tục bằng cách đặt chiến lược worker cho các công cụ thích hợp của công cụ. Cách tốt 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 worker thay vì chiến lược cục bộ có thể làm tăng đáng kể tốc độ biên dịch, tuỳ thuộc vào quá trình triển khai. Đối với Java, bản dựng có thể nhanh hơn 2–4 lần, đôi khi nhiều hơn cho quá trình biên dịch gia tăng. Việc biên dịch Bazel sẽ nhanh hơn khoảng 2,5 lần đối với nhân viê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 bạn 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 cục bộ, bạn có thể sử dụng thử nghiệm chiến lược, trong đó thực thi từ xa và thực thi worker. Để bật chiến lược động, hãy truyền cờ --experiment_spawn_scheduler. Chiến lược này tự động cho phép trình chạy, vì vậy, bạn không cần chỉ định chiến lược worker, nhưng 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ố lượng thực thể worker mặc định cho mỗi tính năng ghi nhớ là 4, nhưng có thể được điều chỉnh bằng cờ worker_max_instances. Có một sự đánh đổi giữa việc sử dụng tốt các CPU có sẵn với dung lượng biên dịch JIT và các lượt truy cập vào bộ nhớ đệm mà bạn nhận được. Với nhiều trình chạy hơn, nhiều mục tiêu hơn sẽ trả chi phí khởi động khi chạy mã không phải JITted và truy cập vào bộ nhớ đệm nguội. Nếu bạn chỉ có một số lượng nhỏ mục tiêu cần xây dựng, thì một worker có thể đánh đổi 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ể worker tối đa trên mỗi bộ nhớ và 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 bạn giữ giá trị mặc định. Đối với các bản dựng gia tăng, lợi ích của nhiều thực thể worker sẽ 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 Xeon 3,5 GHz siêu phân luồng với 64 GB RAM. Đố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 4 bản dựng cuối cùng được sử dụng.

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

Hình 1. Biểu đồ 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, hai worker thực hiện việc biên dịch nhanh nhất, mặc dù chỉ cải thiện 14% so với một worker. Một worker là 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. Các bản dựng sạch tương đối hiếm, nhưng việc thay đổi một tệp duy nhất giữa các quá trình biên dịch là 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 Java có thể ảnh hưởng đến 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 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 20 bản dựng tăng dần có một bản dựng khởi động bị loại bỏ):

Biểu đồ cải tiến hiệu suất của các bản dựng gia tăng

Hình 2. Biểu đồ cải tiến hiệu suất của các bản dựng gia tăng.

Việc tăng tốc độ phụ thuộc vào thay đổi đang được thực hiện. Tốc độ của hệ số 6 được đo trong trường hợp trên khi một hằng số thường dùng được 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 các worker, được khoá bằng tính năng ghi nhớ. Ví dụ: truyền --worker_extra_flag=javac=--debug chỉ bật tính năng gỡ lỗi cho Javac. Chỉ có thể đặt một cờ nhân viên cho mỗi lần sử dụng cờ này và chỉ cho một cờ nhớ. Trình chạy không chỉ được tạo riêng cho từng phương thức ghi nhớ mà còn được tạo cho các biến thể trong cờ khởi động của chúng. Mỗi tổ hợp cờ nhớ và khởi động được kết hợp thành một WorkerKey, và mỗi WorkerKey có thể được tạo tối đa worker_max_instances trình chạy. Hãy xem phần tiếp theo để biết 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 cách ghi nhớ sẽ được ưu tiên chạy các thành phần nhớ ưu tiên thông thường. Điều này có thể giúp ưu tiên các hành động luôn nằm trong đường dẫn quan trọng. Nếu có hai hoặc nhiều trình chạy có mức độ ưu tiên cao thực thi các yêu cầu, thì tất cả các trình chạy khác sẽ bị ngăn chạy. Bạn có thể sử dụng cờ này nhiều lần.

Việc truyền cờ --worker_sandboxing làm cho mỗi yêu cầu trình chạy sử dụng một thư mục hộp cát riêng cho tất cả dữ liệu đầu vào. Quá trình thiết lập hộp cát mất nhiều thời gian hơn, đặc biệt là trên macOS nhưng mang lại sự đảm bảo chính xác hơn.

Cờ --worker_quit_after_build chủ yếu hữu ích cho việc gỡ lỗi và phân tích. Cờ này buộc tất cả trình chạy phải thoát sau khi tạo xong. Bạn cũng có thể chuyển --worker_verbose để nhận được thêm kết quả về những việc mà worker đang làm. Cờ này được phản ánh trong trường verbosity trong WorkRequest, cho phép việc triển khai worker cũng chi tiết hơn.

Worker lưu trữ nhật ký trong thư mục <outputBase>/bazel-workers, chẳng hạn như /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log. Tên tệp bao gồm mã nhân viên và cách ghi nhớ. Vì có thể có nhiều WorkerKey trên mỗi bản ghi nhớ, nên bạn có thể thấy nhiều hơn worker_max_instances tệp nhật ký cho một bản ghi nhớ 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 worker liên tục

Xem trang tạo trình chạy liên tục để biết thêm thông tin về cách tạo một trình chạy.

Ví dụ này minh hoạ cấu hình Starlark cho một trình chạy 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 bạn sử dụng thao tác này sẽ bắt đầu bằng cách 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:

LƯU Ý: Mặc dù thông số kỹ thuật của vùng đệm giao thức sử dụng "trường hợp rắn" (request_id), nhưng giao thức JSON sử dụng "trường hợp lạc đà" (requestId). Trong tài liệu này, chúng ta sẽ sử dụng trường hợp lạc đà trong các ví dụ về JSON, nhưng trường hợp rắn khi nói về trường này 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..." },
  ],
}

Trình chạy này nhận lệnh 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 đó, worker này thực hiện thao tác và gửi WorkResponse theo định dạng JSON đến Bazel trên stdout. Sau đó, Bazel sẽ phân tích cú pháp phản hồi này và tự chuyển đổi thành một proto WorkResponse. Để giao tiếp với worker có liên quan bằng cách sử dụng protobuf mã hoá 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 liên lạc với worker để sử dụng protobuf.

Bazel lấy WorkerKey từ kỷ niệm và các 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, một worker chạy riêng sẽ được tạo ra cho mỗi 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 có quá nhiều biến thể được sử dụng.

Mỗi nhân viên hiện chỉ có thể xử lý một yêu cầu tại một thời điểm. Tính năng thử nghiệm MultiplexWorker cho phép sử dụng nhiều luồng, nếu công cụ cơ bản được đ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 các trình bao bọc 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 packageví dụ về worker worker có thể hữu ích.

Nhân viên ảnh hưởng đến hộp cát như thế nào?

Theo mặc định, việc sử dụng chiến lược worker sẽ không chạy thao tác 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ả trình chạy trong hộp cát, đảm bảo rằng mỗi quá trình thực thi công cụ chỉ thấy các tệp đầu vào cần có. Công cụ này vẫn có thể 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. Việc sử dụng chiến lược dynamic yêu cầu trình chạy phải chạy trong môi trường hộp cát.

Để cho phép sử dụng chính xác bộ nhớ đệm của trình biên dịch với worker, thông báo sẽ được truyền cùng 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 liệu đầu vào có còn hợp lệ mà không cần phải đọc tệp không.

Ngay cả khi sử dụng thông báo đầu vào để bảo vệ bộ nhớ đệm không mong muốn, trình chạy hộp cát cung cấp hộp cát ít nghiêm ngặt hơn hộp cát thuần túy, 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 đó.

Trình chạy Multiplex chỉ có thể được tạo hộp cát nếu quá trình triển khai worker này hỗ trợ và hộp cát này phải được bật riêng với cờ --experimental_worker_multiplex_sandboxing. Xem thêm thông tin chi tiết trong tài liệu thiết kế.

Đọc thêm

Để biết thêm thông tin về nhân viên cố định, hãy xem: