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

รูปที่ 1 กราฟแสดงการปรับปรุงประสิทธิภาพของบิลด์เปล่า
สำหรับการกำหนดค่านี้ ผู้ปฏิบัติงาน 2 คนจะคอมไพล์ได้เร็วที่สุด แม้ว่าจะปรับปรุงได้เพียง 14% เมื่อเทียบกับผู้ปฏิบัติงาน 1 คน ผู้ปฏิบัติงาน 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_extra_flag=javac=--debug จะเปิดใช้การแก้ไขข้อบกพร่องสำหรับ Javac เท่านั้น
คุณตั้งค่าแฟล็กผู้ปฏิบัติงานได้เพียง 1 รายการต่อการใช้แฟล็กนี้ และสำหรับตัวย่อเพียง 1 รายการ
ระบบไม่ได้สร้างผู้ปฏิบัติงานแยกกันสำหรับตัวย่อแต่ละรายการเท่านั้น แต่ยังสร้างสำหรับรูปแบบที่มีแฟล็กการเริ่มต้นที่แตกต่างกันด้วย ระบบจะรวมตัวย่อและแฟล็กการเริ่มต้นแต่ละรายการเข้าด้วยกันเป็น WorkerKey และสร้างผู้ปฏิบัติงานได้สูงสุด worker_max_instances รายการสำหรับ WorkerKey แต่ละรายการ ดูวิธีที่การกำหนดค่า Action สามารถระบุแฟล็กการตั้งค่าได้ในส่วนถัดไป
คุณสามารถใช้แฟล็ก
--high_priority_workers
เพื่อระบุตัวย่อที่ควรทำงานก่อนตัวย่อที่มีลำดับความสำคัญปกติ
ซึ่งจะช่วยจัดลำดับความสำคัญของ Actions ที่อยู่ในเส้นทางวิกฤตเสมอ หากมีผู้ปฏิบัติงานที่มีลำดับความสำคัญสูง 2 รายการขึ้นไปกำลังดำเนินการคำขออยู่ ระบบจะป้องกันไม่ให้ผู้ปฏิบัติงานอื่นๆ ทำงาน คุณใช้แฟล็กนี้ได้หลายครั้ง
การส่งแฟล็ก
--worker_sandboxing
จะทำให้คำขอของผู้ปฏิบัติงานแต่ละรายการใช้ไดเรกทอรี Sandbox แยกกันสำหรับ
อินพุตทั้งหมด การตั้งค่า Sandbox ใช้เวลาเพิ่มขึ้นเล็กน้อย
โดยเฉพาะใน macOS แต่รับประกันความถูกต้องได้ดีขึ้น
แฟล็ก
--worker_quit_after_build
มีประโยชน์หลักๆ สำหรับการแก้ไขข้อบกพร่องและการสร้างโปรไฟล์ แฟล็กนี้จะบังคับให้ผู้ปฏิบัติงานทั้งหมดออกจากระบบเมื่อบิลด์เสร็จสมบูรณ์ นอกจากนี้ คุณยังส่ง
--worker_verbose เพื่อ
รับเอาต์พุตเพิ่มเติมเกี่ยวกับสิ่งที่ผู้ปฏิบัติงานกำลังทำอยู่ได้ด้วย แฟล็กนี้จะแสดงในช่อง verbosity ใน WorkRequest ซึ่งช่วยให้การใช้งาน Worker มีรายละเอียดมากขึ้นด้วย
ผู้ปฏิบัติงานจะจัดเก็บบันทึกในไดเรกทอรี <outputBase>/bazel-workers เช่น
ตัวอย่าง
/tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log
ชื่อไฟล์จะมีรหัสผู้ปฏิบัติงานและตัวย่อ เนื่องจากตัวย่อ 1 รายการอาจมี WorkerKey มากกว่า 1 รายการ คุณจึงอาจเห็นไฟล์บันทึกมากกว่า worker_max_instances รายการสำหรับตัวย่อที่กำหนด
สำหรับบิลด์ Android โปรดดูรายละเอียดที่ หน้าประสิทธิภาพการทำงานของบิลด์ Android
การใช้ผู้ปฏิบัติงานถาวร
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีสร้างผู้ปฏิบัติงานได้ที่หน้าการสร้างผู้ปฏิบัติงานถาวร
ตัวอย่างนี้แสดงการกำหนดค่า Starlark สำหรับผู้ปฏิบัติงานที่ใช้ 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" }
)
เมื่อใช้คำจำกัดความนี้ การใช้ Action นี้ครั้งแรกจะเริ่มต้นด้วยการเรียกใช้บรรทัดคำสั่ง /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..." },
],
}
ผู้ปฏิบัติงานจะได้รับข้อมูลนี้ใน stdin ในรูปแบบ JSON ที่คั่นด้วยการขึ้นบรรทัดใหม่ (เนื่องจากตั้งค่า requires-worker-protocol เป็น JSON) จากนั้นผู้ปฏิบัติงานจะดำเนินการและส่ง WorkResponse ที่จัดรูปแบบ JSON ไปยัง stdout ของ Bazel จากนั้น Bazel จะแยกวิเคราะห์การตอบสนองนี้และแปลงเป็นโปรโตคอล WorkResponse ด้วยตนเอง หากต้องการสื่อสารกับผู้ปฏิบัติงานที่เชื่อมโยงโดยใช้โปรโตคอลบัฟเฟอร์ที่เข้ารหัสแบบไบนารีแทน JSON, requires-worker-protocol จะถูกตั้งค่าเป็น proto ดังนี้
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
หากคุณไม่ใส่ requires-worker-protocol ในข้อกำหนดการดำเนินการ Bazel จะตั้งค่าการสื่อสารของผู้ปฏิบัติงานให้ใช้โปรโตคอลบัฟเฟอร์โดยค่าเริ่มต้น
Bazel จะได้ WorkerKey จากตัวย่อและแฟล็กที่แชร์ ดังนั้นหากการกำหนดค่านี้อนุญาตให้เปลี่ยนพารามิเตอร์ max_mem ระบบจะสร้างผู้ปฏิบัติงานแยกต่างหากสำหรับแต่ละค่าที่ใช้ ซึ่งอาจทำให้ใช้หน่วยความจำมากเกินไปหากใช้ตัวเลือกมากเกินไป
ปัจจุบันผู้ปฏิบัติงานแต่ละคนสามารถประมวลผลคำขอได้ครั้งละ 1 รายการเท่านั้น ฟีเจอร์ผู้ปฏิบัติงานแบบมัลติเพล็กซ์แบบทดลอง อนุญาตให้ใช้หลาย เธรด หากเครื่องมือพื้นฐานเป็นแบบมัลติเธรดและตั้งค่า Wrapper ให้ เข้าใจสิ่งนี้
ใน ที่เก็บ GitHub นี้, คุณจะเห็น Wrapper ผู้ปฏิบัติงานตัวอย่างที่เขียนด้วย Java และ Python หากคุณ ทำงานใน JavaScript หรือ TypeScript แพ็กเกจ @bazel/worker และ ตัวอย่างผู้ปฏิบัติงาน nodejs อาจเป็นประโยชน์
ผู้ปฏิบัติงานส่งผลต่อการจำกัดการเข้าถึงอย่างไร
การใช้กลยุทธ์ worker โดยค่าเริ่มต้นจะไม่เรียกใช้ Action ใน
Sandbox ซึ่งคล้ายกับกลยุทธ์ local คุณสามารถตั้งค่าแฟล็ก --worker_sandboxing เพื่อเรียกใช้ผู้ปฏิบัติงานทั้งหมดภายใน Sandbox เพื่อให้แน่ใจว่าการดำเนินการเครื่องมือแต่ละครั้งจะเห็นเฉพาะไฟล์อินพุตที่ควรมี เครื่องมืออาจยังคงรั่วไหลข้อมูลระหว่างคำขอภายใน เช่น ผ่านแคช การใช้กลยุทธ์ dynamic
กำหนดให้ผู้ปฏิบัติงานต้องอยู่ใน Sandbox
ระบบจะส่ง Digest ไปพร้อมกับไฟล์อินพุตแต่ละไฟล์เพื่อให้ใช้แคชคอมไพเลอร์กับผู้ปฏิบัติงานได้อย่างถูกต้อง คอมไพเลอร์หรือ Wrapper จึงตรวจสอบได้ว่าอินพุตยังคงถูกต้องหรือไม่โดยไม่ต้องอ่านไฟล์
แม้ว่าจะใช้ Digest อินพุตเพื่อป้องกันการแคชที่ไม่ต้องการ แต่ผู้ปฏิบัติงานที่อยู่ใน Sandbox จะมีการจำกัดการเข้าถึงที่เข้มงวดน้อยกว่า Sandbox ที่สมบูรณ์ เนื่องจากเครื่องมืออาจเก็บสถานะภายในอื่นๆ ที่ได้รับผลกระทบจากคำขอก่อนหน้า
ผู้ปฏิบัติงานแบบมัลติเพล็กซ์จะอยู่ในแซนด์บ็อกซ์ได้ก็ต่อเมื่อการใช้งานของผู้ปฏิบัติงานรองรับ และต้องเปิดใช้การทำแซนด์บ็อกซ์นี้แยกกันด้วยแฟล็ก --experimental_worker_multiplex_sandboxing ดูรายละเอียดเพิ่มเติมได้ใน
เอกสารการออกแบบ
อ่านเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ปฏิบัติงานถาวรได้ที่
- บล็อกโพสต์เกี่ยวกับผู้ปฏิบัติงานถาวรฉบับแรก
- คำอธิบายการใช้งาน Haskell {: .external}
- บล็อกโพสต์โดย Mike Morearty {: .external}
- การพัฒนาส่วนหน้าด้วย Bazel: Angular/TypeScript และผู้ปฏิบัติงานถาวร กับ Asana {: .external}
- คำอธิบายกลยุทธ์ Bazel {: .external}
- การสนทนาเกี่ยวกับกลยุทธ์ผู้ปฏิบัติงานที่มีข้อมูลในรายชื่ออีเมล bazel-discuss {: .external}