หน้านี้จะพูดถึงวิธีใช้ผู้ปฏิบัติงานถาวร ประโยชน์ ข้อกำหนด และวิธีที่ผู้ปฏิบัติงานมีผลต่อแซนด์บ็อกซ์
ผู้ปฏิบัติงานถาวรคือกระบวนการที่มีระยะเวลายาวนานที่เริ่มต้นโดยเซิร์ฟเวอร์ Bazel ซึ่งทำหน้าที่เป็น Wrapper รอบเครื่องมือจริง (โดยทั่วไปคือคอมไพเลอร์) หรือเครื่องมือเอง เพื่อประโยชน์จากผู้ปฏิบัติงานถาวร เครื่องมือต้องรองรับการคอมไพล์ตามลำดับ และ Wrapper จะต้องแปลระหว่าง API ของเครื่องมือและรูปแบบคำขอ/ตอบกลับที่อธิบายไว้ด้านล่าง อาจมีการเรียกผู้ปฏิบัติงานคนเดียวกันโดยที่มีและไม่มีแฟล็ก --persistent_worker
ในบิลด์เดียวกัน และมีหน้าที่รับผิดชอบในการเริ่มและสื่อสารกับเครื่องมืออย่างเหมาะสม รวมถึงปิดผู้ปฏิบัติงานเมื่อออกจากระบบ ระบบจะมอบหมายอินสแตนซ์ของผู้ปฏิบัติงานแต่ละรายการ (แต่ไม่ได้แบ่งให้กับ) ไดเรกทอรีการทำงานแยกต่างหากภายใต้ <outputBase>/bazel-workers
การใช้ผู้ปฏิบัติงานถาวรเป็นกลยุทธ์การดำเนินการที่ช่วยลดค่าใช้จ่ายในการดำเนินการเมื่อเริ่มต้นใช้งาน ช่วยให้คอมไพล์ JIT ได้มากขึ้น และช่วยให้การแคชตัวอย่างแผนผังไวยากรณ์นามธรรมในการดำเนินการดำเนินการ กลยุทธ์นี้ทำให้ได้ผลลัพธ์ที่ดีขึ้นด้วยการส่งคำขอหลายรายการไปยังกระบวนการที่ใช้เวลานาน
ผู้ปฏิบัติงานถาวรใช้งานได้ในหลายภาษา ซึ่งรวมถึง Java, Scala, Kotlin และอื่นๆ
โปรแกรมที่ใช้รันไทม์ของ NodeJS สามารถใช้ไลบรารีตัวช่วย @bazel/worker เพื่อนำโปรโตคอลผู้ปฏิบัติงานไปใช้
การใช้ผู้ปฏิบัติงานถาวร
Bazel 0.27 ขึ้นไปใช้ผู้ปฏิบัติงานถาวรโดยค่าเริ่มต้นเมื่อดำเนินการกับบิลด์ แต่การดำเนินการระยะไกลจะมีความสำคัญเหนือกว่า สำหรับการดำเนินการที่ไม่รองรับผู้ปฏิบัติงานถาวร Bazel จะกลับไปที่การเริ่มต้นอินสแตนซ์เครื่องมือสำหรับแต่ละการทำงาน คุณสามารถตั้งค่าบิลด์ให้ใช้ผู้ปฏิบัติงานถาวรอย่างชัดแจ้งได้โดยตั้งค่ากลยุทธ์ worker
สำหรับการจดจำเครื่องมือที่เกี่ยวข้อง แนวทางปฏิบัติแนะนำคือการระบุ 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 การคอมไพล์และ Hit แคชที่ได้รับมีข้อดีข้อเสียคือ เมื่อคนทำงานมากขึ้น กลุ่มเป้าหมายก็จะมีค่าใช้จ่ายมากขึ้นเมื่อเริ่มเรียกใช้โค้ดที่ไม่ได้ JITed และการทำ Coldแคช หากคุณมีเป้าหมายที่จะสร้างไม่มาก ผู้ปฏิบัติงานคนเดียวอาจเลือกระหว่างความเร็วในการคอมไพล์กับการใช้ทรัพยากรได้ดีที่สุด (ตัวอย่างเช่น ดูปัญหา #8586
แฟล็ก worker_max_instances
กำหนดจำนวนอินสแตนซ์ผู้ปฏิบัติงานสูงสุดต่อการตั้งค่าหน่วยความจำและแฟล็ก (ดูด้านล่าง) ดังนั้น ในระบบแบบผสม คุณอาจใช้หน่วยความจำเป็นจำนวนมากหากคุณใช้ค่าเริ่มต้น สำหรับโมเดลเพิ่มเติม ประโยชน์ของอินสแตนซ์ผู้ปฏิบัติงานหลายรายการจะยิ่งน้อยลงไปอีก
กราฟนี้แสดงเวลาการคอมไพล์ตั้งแต่เริ่มต้นสำหรับ Bazel (เป้าหมาย //src:bazel
) บนเวิร์กสเตชันของ Linux แบบไฮเปอร์เทรด Intel Xeon 3.5 GHz แบบ 6 แกนที่มี 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 รายการต่อการใช้แฟล็กนี้ และการช่วยจำได้เพียงรายการเดียวเท่านั้น
ผู้ปฏิบัติงานไม่ได้สร้างขึ้นแยกกันสำหรับการฝึกความช่วยจำแต่ละส่วน แต่รวมถึงตัวแปรต่างๆ ใน Flag สตาร์ทอัพด้วย ชุดค่าผสมของการช่วยจำและแฟล็กเริ่มต้นแต่ละรายการจะรวมเข้าด้วยกันเป็น WorkerKey
และอาจมีการสร้างผู้ปฏิบัติงาน WorkerKey
สูงสุด worker_max_instances
รายการ ดูส่วนถัดไปเพื่อดูว่าการกำหนดค่าการดำเนินการสามารถระบุแฟล็กการตั้งค่าอย่างไร
คุณใช้แฟล็ก --high_priority_workers
เพื่อระบุการช่วยจำที่ควรใช้ในการแจ้งเตือนที่มีลำดับความสำคัญปกติได้ วิธีนี้ช่วยให้คุณจัดลำดับความสำคัญของการดำเนินการที่อยู่ในเส้นทางวิกฤติอยู่เสมอได้ หากมีผู้ปฏิบัติงานที่มีลำดับความสำคัญสูง 2 คนขึ้นไปที่ดำเนินการตามคำขอ ผู้ปฏิบัติงานอื่นๆ ทั้งหมดจะไม่สามารถทำงานได้ ธงนี้ใช้ได้หลายครั้ง
การส่งผ่านแฟล็ก --worker_sandboxing
จะทำให้คำขอของผู้ปฏิบัติงานแต่ละรายใช้ไดเรกทอรีแซนด์บ็อกซ์แยกต่างหากสำหรับอินพุตทั้งหมด การตั้งค่าsandboxใช้เวลาเพิ่มเติม โดยเฉพาะใน macOS แต่รับประกันความถูกต้องได้ดีกว่า
แฟล็ก --worker_quit_after_build
มีประโยชน์ในการแก้ไขข้อบกพร่องและสร้างโปรไฟล์เป็นหลัก แฟล็กนี้บังคับให้คนทำงานทั้งหมด
ต้องปิดเมื่อสร้างเสร็จสมบูรณ์ นอกจากนี้คุณยังส่งผ่าน --worker_verbose
เพื่อรับข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่ผู้ปฏิบัติงานกำลังทำได้อีกด้วย แฟล็กนี้จะแสดงอยู่ในช่อง verbosity
ใน WorkRequest
ช่วยให้การทำให้ใช้งานได้ของผู้ปฏิบัติงานมีความละเอียดมากขึ้น
ผู้ปฏิบัติงานจะจัดเก็บบันทึกของตนเองในไดเรกทอรี <outputBase>/bazel-workers
ตัวอย่างเช่น /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log
ชื่อไฟล์จะมีรหัสผู้ปฏิบัติงานและความทรงจำ เนื่องจากข้อมูลช่วยจําหนึ่งๆ อาจมี WorkerKey
มากกว่า 1 ไฟล์ คุณจึงอาจเห็นไฟล์บันทึกมากกว่า worker_max_instances
ไฟล์สําหรับข้อมูลช่วยจําหนึ่งๆ
สำหรับบิลด์ของ Android ให้ดูรายละเอียดที่หน้า Android Build Performance
การใช้ผู้ปฏิบัติงานถาวร
ดูหน้าการสร้างผู้ปฏิบัติงานถาวรสำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีกำหนดให้ผู้ปฏิบัติงาน
ตัวอย่างนี้แสดงการกำหนดค่า 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" }
)
ด้วยคําจํากัดความนี้ การใช้การดําเนินการครั้งแรกจะเริ่มต้นด้วยการเรียกใช้บรรทัดคำสั่ง /bin/some_compiler -max_mem=4G --persistent_worker
คำขอคอมไพล์ Foo.java
จะมีลักษณะดังนี้
หมายเหตุ: แม้ว่าข้อกำหนดบัฟเฟอร์ของโปรโตคอลจะใช้ "Snake Case" (request_id
) แต่โปรโตคอล JSON ใช้ "camel Case" (requestId
) ในเอกสารนี้ เราจะใช้คำว่า Camel ในตัวอย่าง JSON แต่จะใส่คำว่า Snake เมื่อพูดถึงช่องโดยไม่คำนึงถึงโปรโตคอล
{
"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 ไปยัง Bazel ใน Stdout จากนั้น Bazel จะแยกวิเคราะห์คำตอบนี้และแปลงเป็น Proto WorkResponse
ด้วยตนเอง หากต้องการสื่อสารกับผู้ปฏิบัติงานที่เกี่ยวข้องโดยใช้ protobuf ที่เข้ารหัสแบบไบนารีแทน JSON ต้องตั้งค่า requires-worker-protocol
เป็น proto
ดังนี้
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
หากคุณไม่ใส่ requires-worker-protocol
ในข้อกำหนดการดำเนินการ Bazel จะตั้งค่าเริ่มต้นการสื่อสารสำหรับผู้ปฏิบัติงานเพื่อใช้ protobuf
Bazel ดึง WorkerKey
มาจากฟังก์ชันช่วยจำและแฟล็กที่แชร์ ดังนั้นหากการกำหนดค่านี้อนุญาตให้เปลี่ยนแปลงพารามิเตอร์ max_mem
ระบบจะสร้างผู้ปฏิบัติงานแยกกันสำหรับแต่ละค่าที่ใช้ ซึ่งอาจทำให้มีการใช้หน่วยความจำมากเกินไป
หากใช้หลายรูปแบบมากเกินไป
ปัจจุบันผู้ปฏิบัติงานแต่ละคนจะดำเนินการตามคำขอได้ครั้งละ 1 รายการเท่านั้น ฟีเจอร์ผู้ปฏิบัติงาน Multiplex รุ่นทดลองอนุญาตให้ใช้เทรดหลายรายการ หากเครื่องมือที่สำคัญเป็นมัลติเธรดและมีการตั้งค่า Wrapper ให้เข้าใจเรื่องนี้
ในที่เก็บ GitHub นี้ คุณจะเห็นตัวอย่าง Wrapper ของผู้ปฏิบัติงานที่เขียนด้วย Java และใน Python ถ้าใช้ JavaScript หรือ TypeScript อยู่ คุณอาจใช้แพ็กเกจ@bazel/worker และตัวอย่างผู้ปฏิบัติงาน Nodejs ได้
ผู้ปฏิบัติงานส่งผลต่อแซนด์บ็อกซ์อย่างไร
การใช้กลยุทธ์ worker
โดยค่าเริ่มต้นจะไม่เรียกใช้การดำเนินการในsandbox ซึ่งคล้ายกับกลยุทธ์ local
คุณตั้งค่าแฟล็ก --worker_sandboxing
เพื่อเรียกใช้ผู้ปฏิบัติงานทั้งหมดภายในแซนด์บ็อกซ์ได้ โดยตรวจสอบว่าการดำเนินการของเครื่องมือแต่ละครั้งเห็นเฉพาะไฟล์อินพุตที่ควรจะมีเท่านั้น เครื่องมืออาจยังทำให้ข้อมูลรั่วไหลระหว่างคำขอภายใน เช่น ผ่านทางแคช การใช้กลยุทธ์ dynamic
กำหนดให้ผู้ปฏิบัติงานอยู่ในแซนด์บ็อกซ์
ระบบจะส่งไดเจสต์ไปพร้อมกับไฟล์อินพุตแต่ละไฟล์เพื่อให้ใช้แคชคอมไพเลอร์ได้อย่างถูกต้องสำหรับผู้ปฏิบัติงาน ดังนั้นคอมไพเลอร์หรือ Wrapper จะตรวจสอบได้ว่าอินพุตยังใช้ได้หรือไม่โดยไม่ต้องอ่านไฟล์
แม้จะใช้ไดเจสต์อินพุตเพื่อป้องกันการแคชที่ไม่พึงประสงค์ แต่ผู้ใช้ที่ทำแซนด์บ็อกซ์ก็จะให้บริการแซนด์บ็อกซ์ที่เข้มงวดน้อยกว่าแซนด์บ็อกซ์เพียงอย่างเดียว เนื่องจากเครื่องมืออาจรักษาสถานะภายในอื่นๆ ที่ได้รับผลกระทบจากคำขอก่อนหน้านี้ไว้ได้
ระบบจะแซนด์บ็อกซ์ผู้ปฏิบัติงาน Multiplex ได้ก็ต่อเมื่อการติดตั้งใช้งานของผู้ปฏิบัติงานรองรับเท่านั้น และต้องเปิดใช้แซนด์บ็อกซ์นี้แยกต่างหากด้วยแฟล็ก --experimental_worker_multiplex_sandboxing
ดูรายละเอียดเพิ่มเติมในเอกสารการออกแบบ)
อ่านเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ปฏิบัติงานถาวรได้ที่
- บล็อกโพสต์ต้นฉบับเกี่ยวกับผู้ปฏิบัติงานถาวร
- คำอธิบายการใช้งาน Haaskell {: .external}
- บล็อกโพสต์โดย Mike Morearty {: .external}
- การพัฒนาส่วนหน้าร่วมกับ Bazel: Angular/TypeScript และผู้ปฏิบัติงานถาวร กับ Asana {: .external}
- คำอธิบายเกี่ยวกับกลยุทธ์แบบเบเซล {: .external}
- การพูดคุยเกี่ยวกับกลยุทธ์ผู้ปฏิบัติงานเพื่อให้ข้อมูลในรายชื่ออีเมลแบบ bazel-discuss {: .external}