หน้านี้จะอธิบายวิธีใช้ Worker แบบถาวร ประโยชน์ ข้อกำหนด และวิธีที่ Worker ส่งผลต่อการแซนด์บ็อกซ์
Worker แบบถาวรคือกระบวนการที่ทำงานเป็นเวลานานซึ่งเซิร์ฟเวอร์ Bazel เริ่มต้น โดยจะทำหน้าที่เป็นWrapper รอบเครื่องมือจริง (โดยปกติคือคอมไพเลอร์) หรือเป็นเครื่องมือเอง หากต้องการใช้ประโยชน์จาก Worker แบบถาวร เครื่องมือต้อง
รองรับการคอมไพล์ตามลำดับ และ Wrapper ต้องแปล
ระหว่าง API ของเครื่องมือกับรูปแบบคำขอ/คำตอบที่อธิบายไว้ด้านล่าง Worker เดียวกันอาจเรียกใช้โดยมีและไม่มีแฟล็ก --persistent_worker ในบิลด์เดียวกัน และมีหน้าที่รับผิดชอบในการเริ่มต้นและพูดคุยกับเครื่องมืออย่างเหมาะสม รวมถึงปิด Worker เมื่อออกจากระบบ ระบบจะกำหนดอินสแตนซ์ของ Worker แต่ละรายการ
(แต่ไม่ได้ chroot ไปยัง) ไดเรกทอรีการทำงานแยกต่างหากภายใต้
<outputBase>/bazel-workers
การใช้ Worker แบบถาวรเป็นกลยุทธ์การดำเนินการที่ช่วยลดค่าใช้จ่ายในการเริ่มต้น ช่วยให้คอมไพล์ JIT ได้มากขึ้น และเปิดใช้การแคช เช่น Abstract Syntax Tree ในการดำเนินการของ Action กลยุทธ์นี้ ช่วยให้การปรับปรุงเหล่านี้เกิดขึ้นได้โดยการส่งคำขอหลายรายการไปยังกระบวนการที่ทำงานเป็นเวลานาน
มีการใช้งาน Worker แบบถาวรสำหรับหลายภาษา ซึ่งรวมถึง Java, Scala, Kotlin และอื่นๆ
โปรแกรมที่ใช้รันไทม์ NodeJS สามารถใช้ไลบรารีตัวช่วย @bazel/worker เพื่อ ใช้โปรโตคอล Worker ได้
การใช้ Worker แบบถาวร
Bazel 0.27 ขึ้นไป
จะใช้ Worker แบบถาวรโดยค่าเริ่มต้นเมื่อดำเนินการบิลด์ แม้ว่าการดำเนินการจากระยะไกล
จะมีความสำคัญเหนือกว่าก็ตาม สำหรับดำเนินการที่ไม่รองรับ Worker แบบถาวร
Bazel จะกลับไปเริ่มต้นอินสแตนซ์เครื่องมือสำหรับการดำเนินการแต่ละรายการ คุณสามารถ
ตั้งค่าบิลด์ให้ใช้ Worker แบบถาวรได้อย่างชัดเจนโดยการตั้งค่าworker
กลยุทธ์สำหรับ
ตัวช่วยจำเครื่องมือที่เกี่ยวข้อง แนวทางปฏิบัติแนะนำคือตัวอย่างนี้รวมถึงการระบุ local เป็น
กลยุทธ์สำรองสำหรับ worker
bazel build //my:target --strategy=Javac=worker,localการใช้กลยุทธ์ของ Worker แทนกลยุทธ์ในเครื่องจะช่วยเพิ่มความเร็วในการคอมไพล์ได้อย่างมาก ขึ้นอยู่กับการใช้งาน สําหรับ Java การบิลด์อาจเร็วขึ้น 2-4 เท่า และบางครั้งอาจเร็วกว่านี้สําหรับการคอมไพล์แบบเพิ่ม การคอมไพล์ Bazel จะเร็วขึ้นประมาณ 2.5 เท่าเมื่อใช้ Worker ดูรายละเอียดเพิ่มเติมได้ที่ส่วน "การเลือกจำนวน Worker"
หากคุณมีสภาพแวดล้อมการสร้างจากระยะไกลที่ตรงกับสภาพแวดล้อมการสร้างในเครื่อง คุณสามารถใช้กลยุทธ์แบบไดนามิก
เวอร์ชันทดลอง
ซึ่งจะแข่งกันระหว่างการดำเนินการจากระยะไกลกับการดำเนินการของ Worker หากต้องการเปิดใช้กลยุทธ์แบบไดนามิก
ให้ส่ง Flag --experimental_spawn_scheduler
กลยุทธ์นี้จะเปิดใช้ Worker โดยอัตโนมัติ คุณจึงไม่จำเป็นต้อง
ระบุกลยุทธ์ worker แต่ยังคงใช้ local หรือ sandboxed เป็น
กลยุทธ์สำรองได้
การเลือกจำนวนผู้ปฏิบัติงาน
จำนวนอินสแตนซ์ของ Worker ต่อ Mnemonic จะเป็น 4 โดยค่าเริ่มต้น แต่สามารถปรับได้
ด้วยแฟล็ก
worker_max_instances
การใช้ CPU ที่มีอยู่ให้เกิดประโยชน์สูงสุดและการคอมไพล์ JIT และแคชฮิตที่คุณได้รับนั้นมีข้อแลกเปลี่ยนกัน เมื่อมี Worker มากขึ้น เป้าหมายจำนวนมากขึ้นจะชำระค่าใช้จ่ายเริ่มต้นของการเรียกใช้โค้ดที่ไม่ได้คอมไพล์แบบ JIT และการเข้าถึงแคชที่ไม่มีข้อมูล หากมีเป้าหมายจำนวนน้อยที่ต้องสร้าง Worker เดียวอาจให้
การแลกเปลี่ยนที่ดีที่สุดระหว่างความเร็วในการคอมไพล์และการใช้ทรัพยากร (เช่น
ดูปัญหา #8586
แฟล็ก worker_max_instances จะตั้งค่าจำนวนอินสแตนซ์ของ Worker สูงสุดต่อ
Mnemonic และชุดแฟล็ก (ดูด้านล่าง) ดังนั้นในระบบแบบผสม คุณอาจใช้
หน่วยความจำค่อนข้างมากหากใช้ค่าเริ่มต้น สำหรับการสร้างที่เพิ่มขึ้น ประโยชน์ของอินสแตนซ์ Worker หลายรายการจะยิ่งน้อยลง
กราฟนี้แสดงเวลาในการคอมไพล์ตั้งแต่ต้นสำหรับ Bazel (เป้าหมาย
//src:bazel) ในเวิร์กสเตชัน Linux ที่มี Intel Xeon 3.5 GHz แบบไฮเปอร์เธรด 6 คอร์
พร้อม RAM ขนาด 64 GB สำหรับการกำหนดค่า Worker แต่ละรายการ ระบบจะเรียกใช้การสร้างที่สะอาด 5 รายการและ
ใช้ค่าเฉลี่ยของ 4 รายการสุดท้าย

รูปที่ 1 กราฟการปรับปรุงประสิทธิภาพของบิลด์เปล่า
สำหรับการกำหนดค่านี้ ผู้ปฏิบัติงาน 2 คนจะให้การคอมไพล์ที่เร็วที่สุด แม้ว่าจะมีการปรับปรุงเพียง 14% เมื่อเทียบกับผู้ปฏิบัติงาน 1 คนก็ตาม Worker 1 ตัวเป็นตัวเลือกที่ดีหากคุณต้องการ ใช้หน่วยความจำน้อยลง
โดยทั่วไปแล้วการคอมไพล์แบบเพิ่มจะให้ประโยชน์มากยิ่งขึ้น การบิลด์ใหม่ทั้งหมดค่อนข้างเกิดขึ้นได้ยาก แต่การเปลี่ยนไฟล์เดียวระหว่างการคอมไพล์เป็นเรื่องปกติ โดยเฉพาะในการพัฒนาที่ขับเคลื่อนด้วยการทดสอบ ตัวอย่างข้างต้นยังมีการดำเนินการที่ไม่ใช่ Java ในการแพ็กเกจด้วย ซึ่งอาจบดบังเวลาในการคอมไพล์แบบเพิ่ม
การคอมไพล์แหล่งที่มาของ Java อีกครั้งเท่านั้น
(//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar)
หลังจากเปลี่ยนค่าคงที่สตริงภายในใน
AbstractContainerizingSandboxedSpawn.java
จะช่วยเพิ่มความเร็วได้ 3 เท่า (ค่าเฉลี่ยของการสร้างแบบเพิ่มทีละรายการ 20 รายการโดยทิ้งการสร้างแบบวอร์มอัพ 1 รายการ)

รูปที่ 2 กราฟการปรับปรุงประสิทธิภาพของการสร้างแบบเพิ่ม
การเร่งความเร็วจะขึ้นอยู่กับการเปลี่ยนแปลงที่ดำเนินการ ในสถานการณ์ข้างต้น เมื่อมีการเปลี่ยนแปลงค่าคงที่ที่ใช้กันทั่วไป จะวัดความเร็วที่เพิ่มขึ้นได้ 6 เท่า
การแก้ไขผู้ปฏิบัติงานแบบถาวร
คุณสามารถส่งแฟล็ก
--worker_extra_flag
เพื่อระบุแฟล็กเริ่มต้นให้กับ Worker โดยใช้คีย์เป็นมνηนิค เช่น การส่ง --worker_extra_flag=javac=--debug จะเปิดการแก้ไขข้อบกพร่องสำหรับ Javac เท่านั้น
ตั้งค่า Flag ของ Worker ได้เพียง 1 รายการต่อการใช้ Flag นี้ และใช้ได้กับ Mnemonic เพียงรายการเดียว
ระบบไม่ได้สร้าง Worker แยกกันสำหรับแต่ละ Mnemonic เท่านั้น แต่ยังสร้างสำหรับ
รูปแบบต่างๆ ใน Flag การเริ่มต้นด้วย โดยระบบจะรวมชุดค่าผสมของ Mnemonic และแฟล็กการเริ่มต้นเข้าด้วยกันเป็น WorkerKey และสำหรับ WorkerKey แต่ละรายการ ระบบจะสร้าง Worker ได้สูงสุด worker_max_instances ราย ดูส่วนถัดไปเพื่อดูว่าการกำหนดค่าการดำเนินการยังระบุแฟล็กการตั้งค่าได้อย่างไร
การส่งแฟล็ก
--worker_sandboxing
จะทำให้คำขอของ Worker แต่ละรายการใช้ไดเรกทอรีแซนด์บ็อกซ์แยกต่างหากสำหรับอินพุตทั้งหมด
การตั้งค่าแซนด์บ็อกซ์ต้องใช้เวลาเพิ่มขึ้นเล็กน้อย
โดยเฉพาะใน macOS แต่จะช่วยให้มั่นใจได้ถึงความถูกต้องมากขึ้น นอกเหนือจากรูปแบบบูลีนธรรมดาแล้ว คุณยังกำหนดขอบเขตของแฟล็กให้เป็นมnemonic ของคีย์ Worker ได้ด้วย เช่น --worker_sandboxing=<mnemonic>=<boolean> ซึ่งจะช่วยให้เปิดหรือปิดใช้ Sandbox ต่อ mnemonic ได้ (เช่น --worker_sandboxing --worker_sandboxing=Javac=no) ค่าที่กำหนดในภายหลังจะลบล้างค่าก่อนหน้าสำหรับ mnemonic ที่ค่าเหล่านั้นมีผล ดังนั้น --worker_sandboxing ธรรมดาที่กำหนดในภายหลังจะเปิดใช้ Sandbox อีกครั้งสำหรับ mnemonic ทั้งหมด
โดยแฟล็ก
--worker_quit_after_build
มีประโยชน์หลักๆ ในการแก้ไขข้อบกพร่องและการสร้างโปรไฟล์ แฟล็กนี้จะบังคับให้ Worker ทั้งหมด
ออกเมื่อบิลด์เสร็จสมบูรณ์ นอกจากนี้ คุณยังส่ง
--worker_verbose ไปยัง
เพื่อรับเอาต์พุตเพิ่มเติมเกี่ยวกับสิ่งที่คนงานกำลังทำได้ด้วย โดยแฟล็กนี้จะแสดงในฟิลด์
verbosity ใน WorkRequest ซึ่งจะช่วยให้การใช้งาน Worker มี
รายละเอียดมากขึ้นด้วย
ผู้ปฏิบัติงานจะจัดเก็บบันทึกไว้ในไดเรกทอรี <outputBase>/bazel-workers เช่น
/tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log
ชื่อไฟล์มีรหัส Worker และ Mnemonic เนื่องจากมี WorkerKey ได้มากกว่า 1 รายการต่อมnemonic คุณจึงอาจเห็นไฟล์บันทึกมากกว่า worker_max_instances รายการสำหรับ mnemonic ที่กำหนด
สําหรับการสร้าง Android โปรดดูรายละเอียดที่หน้าประสิทธิภาพการสร้าง Android
การใช้งานผู้ปฏิบัติงานแบบถาวร
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีสร้าง Worker ได้ที่หน้าการสร้าง Worker แบบถาวร
ตัวอย่างนี้แสดงการกำหนดค่า Starlark สำหรับ Worker ที่ใช้ 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" }
)
เมื่อกำหนดค่าเช่นนี้ การใช้การดำเนินการนี้ครั้งแรกจะเริ่มต้นด้วยการเรียกใช้บรรทัดคำสั่ง /bin/some_compiler -max_mem=4G --persistent_worker คำขอ
เพื่อคอมไพล์ Foo.java จะมีลักษณะดังนี้
หมายเหตุ: แม้ว่าข้อกำหนดของบัฟเฟอร์โปรโตคอลจะใช้ "snake case" (request_id)
แต่โปรโตคอล JSON จะใช้ "camel case" (requestId) ในเอกสารนี้ เราจะใช้
camel case ในตัวอย่าง JSON แต่จะใช้ snake case เมื่อพูดถึงฟิลด์
โดยไม่คำนึงถึงโปรโตคอล
{
"arguments": [ "-g", "-source", "1.5", "Foo.java" ]
"inputs": [
{ "path": "symlinkfarm/input1", "digest": "d49a..." },
{ "path": "symlinkfarm/input2", "digest": "093d..." },
],
}
Worker จะได้รับข้อมูลนี้ใน stdin ในรูปแบบ JSON ที่คั่นด้วยการขึ้นบรรทัดใหม่ (เนื่องจากตั้งค่า requires-worker-protocol เป็น JSON) จากนั้น Worker จะดำเนินการ
และส่ง WorkResponse ในรูปแบบ JSON ไปยัง Bazel ใน stdout จากนั้น Bazel จะ
แยกวิเคราะห์การตอบกลับนี้และแปลงเป็น WorkResponse proto ด้วยตนเอง หากต้องการสื่อสารกับ Worker ที่เชื่อมโยงโดยใช้ Protobuf ที่เข้ารหัสแบบไบนารีแทน JSON ให้ตั้งค่า requires-worker-protocol เป็น proto ดังนี้
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
การตั้งค่า requires-worker-protocol ยังช่วยให้มั่นใจได้ว่าระบบจะส่งต่อโปรโตคอลที่เลือกไปยังเซิร์ฟเวอร์การดำเนินการระยะไกลผ่านพร็อพเพอร์ตี้แพลตฟอร์ม persistentWorkerProtocol
หากคุณไม่ใส่ requires-worker-protocol ใน
ข้อกำหนดในการดำเนินการ Bazel จะตั้งค่าเริ่มต้นให้การสื่อสารของ Worker ใช้
protobuf
Bazel จะได้ WorkerKey จากตัวช่วยจำและแฟล็กที่แชร์ ดังนั้นหากการกำหนดค่านี้อนุญาตให้เปลี่ยนพารามิเตอร์ max_mem ระบบจะสร้าง Worker แยกต่างหากสำหรับแต่ละค่าที่ใช้ ซึ่งอาจทำให้ใช้หน่วยความจำมากเกินไปหากใช้ตัวแปรมากเกินไป
ปัจจุบันผู้ปฏิบัติงานแต่ละคนจะประมวลผลคำขอได้ครั้งละ 1 รายการเท่านั้น ฟีเจอร์Multiplex Worker แบบทดลองช่วยให้ใช้หลายเธรดได้ หากเครื่องมือพื้นฐานเป็นแบบมัลติเธรดและตั้งค่า Wrapper ให้เข้าใจเรื่องนี้
ในที่เก็บ GitHub นี้ คุณจะเห็นตัวอย่าง Wrapper ของ Worker ที่เขียนด้วย Java และ Python หากคุณทำงานใน JavaScript หรือ TypeScript แพ็กเกจ@bazel/worker และตัวอย่าง Worker ของ Node.js อาจมีประโยชน์
Worker ส่งผลต่อแซนด์บ็อกซ์อย่างไร
การใช้กลยุทธ์ worker โดยค่าเริ่มต้นจะไม่เรียกใช้การดำเนินการในแซนด์บ็อกซ์ เช่นเดียวกับกลยุทธ์ local คุณสามารถตั้งค่าแฟล็ก
--worker_sandboxing เพื่อเรียกใช้ Worker ทั้งหมดภายในแซนด์บ็อกซ์ เพื่อให้มั่นใจว่าการ
เรียกใช้เครื่องมือแต่ละครั้งจะเห็นเฉพาะไฟล์อินพุตที่ควรมี หรือกำหนดขอบเขต
ต่อ Mnemonic ด้วย --worker_sandboxing=<mnemonic>=<boolean> เครื่องมือ
อาจยังคงรั่วไหลข้อมูลระหว่างคำขอภายใน เช่น ผ่าน
แคช การใช้dynamicกลยุทธ์
กำหนดให้ต้องมีการแซนด์บ็อกซ์พนักงาน
หากต้องการอนุญาตให้ใช้แคชคอมไพเลอร์กับ Worker อย่างถูกต้อง ระบบจะส่งข้อมูลสรุปพร้อมกับไฟล์อินพุตแต่ละไฟล์ ดังนั้นคอมไพเลอร์หรือ Wrapper จึงตรวจสอบได้ว่าอินพุตยังคงถูกต้องหรือไม่โดยไม่ต้องอ่านไฟล์
แม้จะใช้สรุปข้อมูลอินพุตเพื่อป้องกันการแคชที่ไม่ต้องการ แต่ Sandboxed Worker ก็มีการแซนด์บ็อกซ์ที่เข้มงวดน้อยกว่าแซนด์บ็อกซ์แบบเพียว เนื่องจากเครื่องมืออาจ เก็บสถานะภายในอื่นๆ ที่ได้รับผลกระทบจากคำขอก่อนหน้า
Multiplex Worker จะอยู่ในแซนด์บ็อกซ์ได้ก็ต่อเมื่อการใช้งาน Worker รองรับ
และต้องเปิดใช้แซนด์บ็อกซ์นี้แยกต่างหากด้วย
--experimental_worker_multiplex_sandboxing แฟล็ก ดูรายละเอียดเพิ่มเติมได้ใน
เอกสารการออกแบบ)
อ่านเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Worker ที่ทำงานอย่างต่อเนื่องได้ที่