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

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