หน้านี้จะอธิบายวิธีใช้เวิร์กเกอร์แบบถาวร ประโยชน์ ข้อกําหนด และผลกระทบที่เวิร์กเกอร์มีต่อแซนด์บ็อกซ์
เวิร์กเกอร์แบบถาวรคือกระบวนการที่ทำงานต่อเนื่องซึ่งเซิร์ฟเวอร์ Bazel เริ่มต้นขึ้น ซึ่งทำหน้าที่เป็นตัวแฝงรอบเครื่องมือจริง (โดยทั่วไปคือคอมไพเลอร์) หรือเป็นเครื่องมือนั้นเอง เครื่องมือต้องรองรับการคอมไพล์ตามลำดับ และรัปเปอร์ต้องแปลระหว่าง API ของเครื่องมือกับรูปแบบคำขอ/คำตอบที่อธิบายไว้ด้านล่าง เพื่อให้ได้รับประโยชน์จากเวิร์กเกอร์แบบถาวร อาจมีเรียกใช้ Worker เดียวกันโดยใส่หรือไม่ใส่ Flag --persistent_worker
ในบิลด์เดียวกัน และมีหน้าที่รับผิดชอบในการเริ่มต้นและพูดคุยกับเครื่องมืออย่างเหมาะสม รวมถึงปิด Worker เมื่อออก ระบบจะกำหนดอินสแตนซ์ของ W worker แต่ละรายการ (แต่ไม่ได้เปลี่ยนเส้นทางไปยัง) ไดเรกทอรีการทำงานแยกต่างหากภายใต้ <outputBase>/bazel-workers
การใช้ผู้ปฏิบัติงานถาวรเป็นกลยุทธ์การดำเนินการที่ลดโอเวอร์เฮดเริ่มต้น ทำให้มีการคอมไพล์ JIT มากขึ้น และเปิดใช้การแคช เช่น ต้นไม้ไวยากรณ์นามธรรมในการดำเนินการเพื่อดำเนินการ กลยุทธ์นี้ทำให้การปรับปรุงเหล่านี้บรรลุผลโดยส่งคำขอหลายรายการไปยังกระบวนการที่ทำงานเป็นเวลานาน
มีการใช้งานผู้ปฏิบัติงานถาวรสำหรับหลายภาษา ซึ่งรวมถึง Java, Scala, Kotlin และอื่นๆ
โปรแกรมที่ใช้รันไทม์ NodeJS สามารถใช้ไลบรารีตัวช่วย @bazel/worker เพื่อติดตั้งใช้งานโปรโตคอลสำหรับแรงงาน
การใช้ผู้ปฏิบัติงานแบบถาวร
Bazel 0.27 ขึ้นไปจะใช้เวิร์กเกอร์แบบถาวรโดยค่าเริ่มต้นเมื่อทำการบิลด์ แม้ว่าการดําเนินการจากระยะไกลจะมีความสำคัญมากกว่า สำหรับการดำเนินการที่ไม่รองรับคนทำงานถาวร Bazel จะกลับไปเริ่มอินสแตนซ์เครื่องมือสำหรับแต่ละการดำเนินการ คุณสามารถตั้งค่าบิลด์ให้ใช้ผู้ปฏิบัติงานแบบถาวรได้อย่างชัดเจนโดยการตั้งค่าworker
strategy สำหรับคําช่วยจําของเครื่องมือที่เกี่ยวข้อง แนวทางปฏิบัติแนะนำคือให้ระบุ local
เป็นกลยุทธ์สำรองสำหรับกลยุทธ์ worker
ดังตัวอย่างต่อไปนี้
bazel build //my:target --strategy=Javac=worker,local
การใช้กลยุทธ์สำหรับโหนดทำงานแทนกลยุทธ์ในเครื่องสามารถเพิ่มความเร็วในการคอมไพล์ได้อย่างมาก ทั้งนี้ขึ้นอยู่กับการใช้งาน สำหรับ Java การสร้างจะเร็วขึ้น 2-4 เท่า และบางครั้งอาจเร็วกว่านั้นสำหรับการคอมไพล์แบบเพิ่ม การคอมไพล์ Bazel นั้น รวดเร็วกว่าประมาณ 2.5 เท่าสำหรับพนักงาน โปรดดูรายละเอียดเพิ่มเติมที่ส่วน "การเลือกจำนวนผู้ปฏิบัติงาน"
หากมีสภาพแวดล้อมบิลด์ระยะไกลที่ตรงกับสภาพแวดล้อมบิลด์ในเครื่อง คุณจะใช้กลยุทธ์ dynamic เวอร์ชันทดลอง ซึ่งจะแข่งกับการดำเนินการระยะไกลและการดำเนินการของผู้ปฏิบัติงานได้ หากต้องการเปิดใช้กลยุทธ์แบบไดนามิก ให้ส่งค่าสถานะ --experimental_spawn_scheduler
กลยุทธ์นี้จะเปิดใช้แรงงานโดยอัตโนมัติ คุณจึงไม่ต้องระบุกลยุทธ์ worker
แต่ยังคงใช้ local
หรือ sandboxed
เป็นกลยุทธ์สำรองได้
การเลือกจํานวนผู้ปฏิบัติงาน
จำนวนอินสแตนซ์ผู้ปฏิบัติงานเริ่มต้นต่อคีย์สั้นคือ 4 แต่สามารถปรับได้ด้วยตัวเลือกworker_max_instances
การใช้ CPU ที่มีอยู่อย่างคุ้มค่าและจำนวนการคอมไพล์ JIT และการตีคู่แคชที่คุณได้รับนั้นมีความเกี่ยวข้องกัน เมื่อมีผู้ปฏิบัติงานมากขึ้น เป้าหมายจำนวนมากขึ้นจะต้องจ่ายค่าเริ่มต้นของการทำงานโค้ดที่ไม่ใช่ JIT และการใช้แคชเย็น หากคุณมีเป้าหมายที่จะสร้างจำนวนน้อย ผู้ปฏิบัติงานคนเดียวอาจให้ผลดีระหว่างความเร็วในการคอมไพล์กับการใช้งานทรัพยากร (ตัวอย่างเช่น ดูปัญหา #8586
Flag worker_max_instances
จะตั้งค่าจํานวนอินสแตนซ์ที่ทำงานสูงสุดต่อชุดคําช่วยจําและ Flag (ดูด้านล่าง) ดังนั้นในระบบแบบผสม คุณอาจต้องใช้หน่วยความจําค่อนข้างมากหากใช้ค่าเริ่มต้น สำหรับบิลด์ที่เพิ่มขึ้นเรื่อยๆ ประโยชน์ของอินสแตนซ์ผู้ปฏิบัติงานหลายรายการจะยิ่งน้อยลง
กราฟนี้แสดงเวลาคอมไพล์จากรอยขีดข่วนสำหรับ Bazel (เป้าหมาย //src:bazel
) บนเวิร์กสเตชัน Intel Xeon 3.5 GHz Linux แบบ 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 เท่าจะวัดได้ในสถานการณ์ข้างต้นเมื่อมีการเปลี่ยนแปลงค่าคงที่ที่ใช้กันโดยทั่วไป
การแก้ไขผู้ปฏิบัติงานแบบถาวร
คุณสามารถส่ง Flag --worker_extra_flag
เพื่อระบุ Flag เริ่มต้นให้กับ Worker โดยคีย์ตามคําช่วยจํา ตัวอย่างเช่น การส่ง --worker_extra_flag=javac=--debug
จะเปิดการแก้ไขข้อบกพร่องสำหรับ Javac เท่านั้น
คุณตั้งค่า Flag ของ WOrker ได้เพียง 1 รายการต่อการใช้ Flag นี้ และสำหรับคําช่วยจํารายการเดียวเท่านั้น
ระบบไม่ได้สร้างผู้ปฏิบัติงานแยกกันสำหรับคําช่วยจําแต่ละรายการเท่านั้น แต่ยังสร้างสําหรับรูปแบบต่างๆ ของ Flag การเริ่มต้นด้วย ชุดค่าผสมของ mnemonic และ flag เริ่มต้นแต่ละชุดจะรวมกันเป็น WorkerKey
และระบบอาจสร้างผู้ปฏิบัติงานได้สูงสุด worker_max_instances
คนสำหรับ WorkerKey
แต่ละรายการ โปรดดูส่วนถัดไปเกี่ยวกับวิธีที่การกำหนดค่าการดำเนินการสามารถระบุแฟล็กการตั้งค่า
คุณสามารถใช้ Flag --high_priority_workers
เพื่อระบุคําช่วยจําที่ควรเรียกใช้แทนคําช่วยจําที่มีลําดับความสําคัญเป็นปกติ ซึ่งจะช่วยจัดลําดับความสําคัญของการดำเนินการที่อยู่ในเส้นทางสําคัญเสมอ หากมีผู้ปฏิบัติงานที่มีลําดับความสําคัญสูงดําเนินการตามคําขอ 2 คนขึ้นไป ระบบจะป้องกันไม่ให้ผู้ปฏิบัติงานคนอื่นๆ ทํางาน คุณใช้ Flag นี้ซ้ำได้
การส่งผ่าน Flag --worker_sandboxing
ทำให้ผู้ปฏิบัติงานแต่ละคำขอใช้ไดเรกทอรีแซนด์บ็อกซ์แยกต่างหากสำหรับอินพุตทั้งหมด การตั้งค่า Sandbox จะใช้เวลาเพิ่มเติมอีกสักหน่อย โดยเฉพาะใน macOS แต่จะให้การรับประกันความถูกต้องที่ดีขึ้น
การแจ้ง --worker_quit_after_build
มีประโยชน์เป็นหลักในการแก้ไขข้อบกพร่องและการทำโปรไฟล์ ธงนี้จะบังคับให้ผู้ปฏิบัติงานทั้งหมด
ปิดการทำงานเมื่อบิลด์เสร็จแล้ว และคุณยังส่ง --worker_verbose
เพื่อรับข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่พนักงานกำลังทำอยู่ได้อีกด้วย แฟล็กนี้จะแสดงในฟิลด์ verbosity
ใน WorkRequest
ซึ่งช่วยให้การติดตั้งใช้งาน Executor แสดงผลได้ละเอียดยิ่งขึ้นด้วย
ผู้ปฏิบัติงานจะจัดเก็บบันทึกไว้ในไดเรกทอรี <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
จะมีลักษณะดังนี้
หมายเหตุ: แม้ว่าข้อกำหนดบัฟเฟอร์โปรโตคอลจะใช้ "รูปแบบงู" (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 ใน Stouts จากนั้น Bazel แยกวิเคราะห์คำตอบนี้และแปลงเป็น Pro ของ WorkResponse
ด้วยตนเอง หากต้องการสื่อสารกับแรงงานที่เชื่อมโยงโดยใช้ protobuf ที่เข้ารหัสไบนารีแทน JSON ให้ตั้งค่า requires-worker-protocol
เป็น proto
ดังนี้
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
หากไม่รวม requires-worker-protocol
ไว้ในข้อกำหนดการดำเนินการ Bazel จะกำหนดค่าเริ่มต้นการสื่อสารสำหรับผู้ปฏิบัติงานให้ใช้ Protocolbuf
Bazel ดึงข้อมูล WorkerKey
มาจากเทคนิคการจำและแฟล็กที่แชร์ ดังนั้นหากการกำหนดค่านี้อนุญาตให้เปลี่ยนพารามิเตอร์ max_mem
ระบบจะสร้างผู้ปฏิบัติงานแยกกันสำหรับแต่ละค่าที่ใช้ ซึ่งอาจทำให้เกิดการใช้หน่วยความจำมากเกินไป
หากใช้หลายรูปแบบเกินไป
ปัจจุบันผู้ปฏิบัติงานแต่ละคนประมวลผลคำขอได้ทีละ 1 คำขอเท่านั้น ฟีเจอร์ผู้ปฏิบัติงานมัลติเพล็กซ์รุ่นทดลองอนุญาตให้ใช้ชุดข้อความหลายรายการได้ หากเครื่องมือที่สำคัญเป็นแบบมัลติเทรดและมีการตั้งค่า Wrapper ให้ทำความเข้าใจเรื่องนี้
ในที่เก็บ GitHub นี้ คุณสามารถดูตัวอย่าง Wrapper ของผู้ปฏิบัติงานที่เขียนด้วย Java และใน Python หากคุณทํางานใน JavaScript หรือ TypeScript แพ็กเกจ @bazel/worker และตัวอย่าง nodejs worker อาจมีประโยชน์
ผู้ปฏิบัติงานส่งผลต่อแซนด์บ็อกซ์อย่างไร
การใช้กลยุทธ์ worker
โดยค่าเริ่มต้นจะไม่เรียกใช้การดําเนินการในแซนด์บ็อกซ์ ซึ่งคล้ายกับกลยุทธ์ local
คุณสามารถตั้งค่า Flag --worker_sandboxing
เพื่อเรียกใช้เวิร์กเกอร์ทั้งหมดภายในแซนด์บ็อกซ์ เพื่อให้แน่ใจว่าการเรียกใช้เครื่องมือแต่ละครั้งจะเห็นเฉพาะไฟล์อินพุตที่ควรจะเห็น เครื่องมือนี้อาจยังทำให้ข้อมูลระหว่างคำขอรั่วไหลภายใน เช่น ผ่านแคช การใช้กลยุทธ์ dynamic
กำหนดให้ผู้ปฏิบัติงานต้องแซนด์บ็อกซ์
ระบบจะส่งข้อมูลสรุปไปพร้อมกับไฟล์อินพุตแต่ละไฟล์เพื่อให้ใช้แคชคอมไพเลอร์กับเวิร์กเกอร์ได้อย่างถูกต้อง ดังนั้นคอมไพเลอร์หรือ Wrapper จะตรวจสอบได้ว่าอินพุตยังคงถูกต้องหรือไม่โดยไม่ต้องอ่านไฟล์
แม้ในขณะที่ใช้ไดเจสต์อินพุตเพื่อป้องกันการแคชที่ไม่พึงประสงค์ ผู้ปฏิบัติงานที่ใช้แซนด์บ็อกซ์ก็มีแซนด์บ็อกซ์ที่เข้มงวดน้อยกว่าแซนด์บ็อกซ์อย่างแท้จริง เนื่องจากเครื่องมืออาจรักษาสถานะภายในอื่นๆ ที่ได้รับผลกระทบจากคำขอก่อนหน้าไว้
คุณจะจัดเก็บเวิร์กเกอร์แบบหลายรายการไว้ในแซนด์บ็อกซ์ได้ก็ต่อเมื่อการติดตั้งใช้งานเวิร์กเกอร์รองรับเท่านั้น และจะต้องเปิดใช้แซนด์บ็อกซ์นี้แยกต่างหากด้วย Flag --experimental_worker_multiplex_sandboxing
ดูรายละเอียดเพิ่มเติมได้ในเอกสารการออกแบบ)
อ่านเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ปฏิบัติงานแบบถาวรได้ที่