หน้านี้จะพูดถึงวิธีใช้ผู้ปฏิบัติงานถาวร ประโยชน์ ข้อกำหนด และวิธีที่ผู้ปฏิบัติงานส่งผลต่อแซนด์บ็อกซ์
ผู้ปฏิบัติงานถาวรเป็นกระบวนการที่ทำงานมาอย่างยาวนานที่เริ่มต้นโดยเซิร์ฟเวอร์ Bazel ซึ่งทำหน้าที่เป็นเครื่องตัดคำรอบเครื่องมือจริง (โดยทั่วไปคือคอมไพเลอร์) หรือเป็นเครื่องมือเอง เพื่อให้ได้ประโยชน์จากผู้ปฏิบัติงานถาวร เครื่องมือต้องรองรับการรวบรวมตามลำดับ และ 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 แคชที่คุณได้รับ เมื่อมีคนทำงานมากขึ้น ก็มีเป้าหมายที่จะจ่ายค่าใช้จ่ายในการเริ่มต้นเรียกใช้โค้ดที่ไม่ใช่ JITted และเกิดการแคชแคช หากคุณมีเป้าหมายที่จะสร้างไม่มาก ผู้ปฏิบัติงานคนเดียวอาจให้สมดุลที่สุดระหว่างความเร็วในการคอมไพล์และการใช้งานทรัพยากร (ตัวอย่างเช่น ดูปัญหา #8586
แฟล็ก worker_max_instances
กำหนดจำนวนอินสแตนซ์ของผู้ปฏิบัติงานสูงสุดต่อชุดหน่วยความจำและชุดแฟล็ก (ดูด้านล่าง) ดังนั้นในระบบแบบผสม คุณอาจใช้หน่วยความจำจำนวนมากหากเก็บค่าเริ่มต้นไว้ การสร้างประโยชน์เพิ่มเติม
ของอินสแตนซ์ผู้ปฏิบัติงานหลายรายการจะยิ่งน้อยลงไปอีก
กราฟนี้แสดงเวลาการคอมไพล์จากรอยขูดสำหรับ Bazel (เป้าหมาย
//src:bazel
) บนเวิร์กสเตชัน Intel Xeon 3.5 GHz แบบไฮเปอร์เทรด 6 แกนของ Linux ที่มี 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 รายการต่อการใช้แฟล็กนี้และการจำเพียงครั้งเดียว
ผู้ปฏิบัติงานไม่ได้สร้างขึ้นมาแยกกันสำหรับการช่วยจำแต่ละรายการ แต่รวมถึงตัวแปรต่างๆ ในการแฟล็กการเริ่มต้นด้วย การรวมการช่วยจำและแฟล็กสตาร์ทอัพแต่ละรายการจะรวมกันเป็น WorkerKey
และสำหรับ WorkerKey
แต่ละรายการจะมีการสร้างผู้ปฏิบัติงานได้สูงสุด worker_max_instances
คน ดูส่วนถัดไปเพื่อดูวิธีที่การกำหนดค่าการดำเนินการระบุ Flag การตั้งค่า
คุณใช้แฟล็ก --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
การติดตั้งใช้งานผู้ปฏิบัติงานถาวร
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีกำหนดผู้ปฏิบัติงานได้ที่หน้าการสร้างผู้ปฏิบัติงานถาวร
ตัวอย่างนี้แสดงการกำหนดค่า 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 ใช้ "เคสแบบอูฐ" (requestId
) ในเอกสารนี้ เราจะใช้
ตัวพิมพ์ Camel ในตัวอย่าง JSON แต่ใช้ลักษณะตัวพิมพ์แบบงูเมื่อพูดถึงฟิลด์
โดยไม่คำนึงถึงโปรโตคอล
{
"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
ด้วยตนเอง หากต้องการสื่อสารกับผู้ปฏิบัติงานที่เกี่ยวข้องโดยใช้ Protocolbuf ที่เข้ารหัสแบบไบนารีแทน 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
ดูรายละเอียดเพิ่มเติมได้ในเอกสารการออกแบบ)
อ่านเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ปฏิบัติงานถาวรได้ที่