หน้านี้จะอธิบายวิธีใช้ Worker แบบถาวร ประโยชน์ ข้อกำหนด และวิธีที่ Worker ส่งผลต่อการจำกัดการเข้าถึง
Worker แบบถาวรคือกระบวนการที่ทำงานเป็นเวลานานซึ่งเริ่มต้นโดยเซิร์ฟเวอร์ Bazel โดยทำหน้าที่เป็น Wrapper รอบๆ เครื่องมือจริง (โดยปกติจะเป็นคอมไพเลอร์) หรือเป็น เครื่องมือเอง เครื่องมือต้องรองรับการคอมไพล์ตามลำดับ และ Wrapper ต้องแปลระหว่าง API ของเครื่องมือกับรูปแบบคำขอ/การตอบสนองที่อธิบายไว้ด้านล่างเพื่อให้ได้รับประโยชน์จาก Worker แบบถาวร ระบบอาจเรียก Worker รายเดียวกันโดยมีและไม่มีแฟล็ก --persistent_worker ในบิลด์เดียวกัน และ Worker มีหน้าที่รับผิดชอบในการเริ่มต้นและสื่อสารกับเครื่องมืออย่างเหมาะสม รวมถึงการปิด Worker เมื่อออกจากระบบ ระบบจะกำหนดไดเรกทอรีการทำงานแยกต่างหากภายใต้
<outputBase>/bazel-workersให้กับอินสแตนซ์ Worker แต่ละรายการ
(แต่ไม่ได้จำกัดการเข้าถึงรูทไดเรกทอรี)
การใช้ 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 สามารถระบุแฟล็กการตั้งค่าได้ในส่วนถัดไป
การส่งแฟล็ก
--worker_sandboxing
จะทำให้คำขอ Worker แต่ละรายการใช้ไดเรกทอรี Sandbox แยกกันสำหรับอินพุตทั้งหมด
การตั้งค่า Sandbox ใช้เวลาเพิ่มขึ้นเล็กน้อย
โดยเฉพาะใน 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 ที่เชื่อมโยงโดยใช้โปรโตคอลบัฟเฟอร์ที่เข้ารหัสแบบไบนารีแทน
JSON, requires-worker-protocol จะถูกตั้งค่าเป็น proto ดังนี้
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
หากคุณไม่รวม requires-worker-protocol ไว้ในข้อกำหนดการดำเนินการ Bazel จะตั้งค่าเริ่มต้นการสื่อสาร Worker ให้ใช้โปรโตคอลบัฟเฟอร์
Bazel จะสร้าง WorkerKey จากตัวย่อและแฟล็กที่แชร์ ดังนั้นหากการกำหนดค่านี้อนุญาตให้เปลี่ยนพารามิเตอร์ max_mem ระบบจะสร้าง Worker แยกกันสำหรับแต่ละค่าที่ใช้ ซึ่งอาจทำให้ใช้หน่วยความจำมากเกินไปหากใช้ค่าที่แตกต่างกันมากเกินไป
ปัจจุบัน Worker แต่ละรายการสามารถประมวลผลคำขอได้ครั้งละ 1 รายการเท่านั้น ฟีเจอร์ Worker แบบมัลติเพล็กซ์แบบทดลอง อนุญาตให้ใช้หลาย เธรด หากเครื่องมือพื้นฐานเป็นแบบมัลติเธรดและตั้งค่า Wrapper ให้ เข้าใจฟีเจอร์นี้
คุณสามารถดู Wrapper Worker ตัวอย่างที่เขียนด้วย Java และ Python ได้ในที่เก็บ GitHub นี้ หากคุณ ทำงานใน JavaScript หรือ TypeScript แพ็กเกจ @bazel/worker และ ตัวอย่าง Worker ของ Node.js อาจเป็นประโยชน์
Worker ส่งผลต่อการจำกัดการเข้าถึงอย่างไร
การใช้กลยุทธ์ worker โดยค่าเริ่มต้นจะไม่เรียกใช้ Action ใน
Sandbox ซึ่งคล้ายกับกลยุทธ์ local คุณสามารถตั้งค่าแฟล็ก --worker_sandboxing เพื่อเรียกใช้ Worker ทั้งหมดภายใน Sandbox เพื่อให้แน่ใจว่าการดำเนินการเครื่องมือแต่ละครั้งจะเห็นเฉพาะไฟล์อินพุตที่ควรมี เครื่องมืออาจยังคงรั่วไหลข้อมูลระหว่างคำขอภายใน เช่น ผ่านแคช การใช้กลยุทธ์ dynamic
กำหนดให้ Worker ต้องอยู่ใน Sandbox
ระบบจะส่ง Digest ไปพร้อมกับไฟล์อินพุตแต่ละไฟล์เพื่อให้ใช้แคชคอมไพเลอร์กับ Worker ได้อย่างถูกต้อง คอมไพเลอร์หรือ Wrapper จึงตรวจสอบได้ว่าอินพุตยังคงถูกต้องหรือไม่โดยไม่ต้องอ่านไฟล์
แม้จะใช้ Digest อินพุตเพื่อป้องกันการแคชที่ไม่ต้องการ แต่ Worker ที่อยู่ใน Sandbox จะมีการจำกัดการเข้าถึงที่เข้มงวดน้อยกว่า Sandbox แบบเพียว เนื่องจากเครื่องมืออาจเก็บสถานะภายในอื่นๆ ที่ได้รับผลกระทบจากคำขอก่อนหน้า
Worker แบบมัลติเพล็กซ์จะอยู่ใน Sandbox ได้ก็ต่อเมื่อการใช้งาน Worker รองรับ และต้องเปิดใช้การจำกัดการเข้าถึงนี้แยกกันด้วยแฟล็ก --experimental_worker_multiplex_sandboxing ดูรายละเอียดเพิ่มเติมได้ใน
เอกสารการออกแบบ)
อ่านเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Worker แบบถาวรได้ที่