หน้านี้จะพูดถึงวิธีใช้ผู้ปฏิบัติงานถาวร ประโยชน์ ข้อกําหนด และผลกระทบที่ผู้ปฏิบัติงานมีต่อแซนด์บ็อกซ์
ผู้ปฏิบัติงานถาวรคือกระบวนการที่ใช้เวลานานซึ่งเริ่มต้นโดยเซิร์ฟเวอร์ 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_doubleclickr กลยุทธ์นี้เปิดใช้ผู้ปฏิบัติงานโดยอัตโนมัติ คุณจึงไม่จําเป็นต้องระบุกลยุทธ์ worker
แต่คุณยังคงใช้ local
หรือ sandboxed
เป็นโฆษณาสํารองได้
เลือกจํานวนผู้ปฏิบัติงาน
จํานวนอินสแตนซ์ของผู้ปฏิบัติงานเริ่มต้นต่อความทรงจําคือ 4 รายการ แต่สามารถปรับเปลี่ยนได้ด้วยการติดธง worker_max_instances
การใช้ CPU ที่มีอยู่และ
การคอมไพล์ JIT กับ Hit แคชที่คุณได้รับนั้นมีทั้งข้อดีและข้อเสีย เมื่อมีผู้ปฏิบัติงานมากขึ้น เป้าหมายเพิ่มเติมก็จะมีค่าใช้จ่ายเพิ่มเติมในการเรียกใช้โค้ดที่ไม่ใช่ JIT และการเกิดแคชที่เย็น หากคุณมีเป้าหมายจํานวนไม่มากที่จะสร้าง ผู้ปฏิบัติงานรายเดียวอาจให้ผลลัพธ์ที่ดีที่สุดระหว่างการรวบรวมคลิปกับการใช้งานทรัพยากร (เช่น ดูปัญหา #8586
แฟล็ก worker_max_instances
จะตั้งอินสแตนซ์ของผู้ปฏิบัติงานสูงสุดต่อชุดหน่วยความจําและชุดธง (ดูด้านล่าง) ดังนั้น ในระบบผสม คุณอาจต้องใช้หน่วยความจําจํานวนมากหากเก็บค่าเริ่มต้นไว้ สําหรับบิลด์ที่เพิ่มขึ้น
ประโยชน์ของอินสแตนซ์ผู้ปฏิบัติงานหลายรายการจะยิ่งเล็กลง
กราฟนี้แสดงเวลาที่รวบรวมจาก Scratch สําหรับ Bazel (เป้าหมาย //src:bazel
) ในเวิร์กสเตชัน Intel Xeon 3.5 GHz แบบ 6 แกนที่มี RAM 64 GB สําหรับการกําหนดค่าผู้ปฏิบัติงานแต่ละรายการ จะมีการสร้างบิลด์ที่สะอาด 5 แบบและระบบจะใช้ค่าเฉลี่ยของ 4 เวอร์ชันล่าสุด
รูปที่ 1 กราฟของการปรับปรุงประสิทธิภาพของบิลด์ที่สะอาด
สําหรับการกําหนดค่านี้ ผู้ปฏิบัติงาน 2 คนจะให้การคอมไพล์ที่เร็วที่สุด แม้ว่าจะมีการปรับปรุงเพียง 14% เมื่อเทียบกับผู้ปฏิบัติงาน 1 ราย ผู้ปฏิบัติงาน 1 คนเป็นตัวเลือกที่ดีหากคุณต้องการใช้หน่วยความจําน้อยลง
แต่การรวบรวมคลิปมักจะมีประโยชน์มากกว่านั้น การสร้างเวอร์ชันที่สะอาดนั้นแทบจะไม่ค่อยเกิดขึ้น แต่การเปลี่ยนไฟล์เดียวระหว่างคอมไพล์นั้นเป็นเรื่องปกติ โดยเฉพาะอย่างยิ่งในขั้นตอนการพัฒนาสําหรับการทดสอบ ตัวอย่างข้างต้นยังมีการดําเนินการอื่นๆ ที่ไม่ใช่การบรรจุหีบห่อซึ่งอาจทําให้เวลาคอมไพล์เพิ่มขึ้น
การรวบรวมแหล่งที่มาของ Java เท่านั้น
(//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar
)
หลังจากเปลี่ยนค่าคงที่ภายในของสตริงใน AbstractContainerizingSandboxedSpawn.java แล้ว ช่วยเพิ่มความเร็วได้ 3 เท่า (โดยเฉลี่ยของบิลด์ที่เพิ่มขึ้น 20 รายการและมีการสร้าง Warmup 1 รายการ) ดังนี้
รูปที่ 2 กราฟของการปรับปรุงประสิทธิภาพของบิลด์ที่เพิ่มขึ้น
ความเร็วขึ้นอยู่กับการเปลี่ยนแปลงที่เกิดขึ้น การเร่งความเร็วของปัจจัย 6 ในสถานการณ์ข้างต้นเมื่อค่าคงที่ที่ใช้กันทั่วไปมีการเปลี่ยนแปลง
การแก้ไขผู้ปฏิบัติงานถาวร
คุณสามารถส่งธง --worker_extra_flag
เพื่อระบุการติดธงสตาร์ทอัพให้กับผู้ปฏิบัติงาน ซึ่งคีย์ช่วยจําได้ด้วย ตัวอย่างเช่น การส่ง --worker_extra_flag=javac=--debug
จะเปิดการแก้ไขข้อบกพร่องสําหรับ Javac เท่านั้น
ตั้งค่าการติดธงผู้ปฏิบัติงานได้เพียง 1 รายการเท่านั้นต่อการตั้งค่าสถานะนี้ และใช้ได้กับหน่วยความจําเท่านั้น 1 รายการ
ผู้ปฏิบัติงานไม่ได้สร้างขึ้นแยกกันสําหรับแต่ละความทรงจําเท่านั้น แต่ยังรวมถึงตัวแปรต่างๆ ในสตาร์ทอัพด้วย การรวมการติดธงหน่วยความจําและการเริ่มต้นแต่ละครั้งเข้าด้วยกันจะรวมเป็น WorkerKey
และสร้าง WorkerKey
ให้กับผู้ปฏิบัติงานได้สูงสุด worker_max_instances
คน โปรดดูส่วนถัดไปเกี่ยวกับวิธีที่การกําหนดค่าการดําเนินการจะระบุแฟล็กการตั้งค่าได้ด้วย
คุณใช้แฟล็ก --high_priority_workers
เพื่อระบุหน่วยความจําที่ต้องการซึ่งควรจะใช้กับคําสั่ง "ลําดับความสําคัญ" แบบปกติได้ การดําเนินการนี้จะช่วยจัดลําดับความสําคัญของการดําเนินการที่อยู่ในเส้นทางที่สําคัญเสมอ หากมีผู้ปฏิบัติงานที่มีลําดับความสําคัญสูงอย่างน้อย 2 คนดําเนินการคําขอ ระบบจะหยุดผู้ปฏิบัติงานคนอื่นๆ ทั้งหมด ธงนี้ใช้ได้หลายครั้ง
การส่งการแจ้งว่าไม่เหมาะสม --worker_sandboxing
จะทําให้ผู้ปฏิบัติงานแต่ละรายใช้ไดเรกทอรีแซนด์บ็อกซ์แยกต่างหากสําหรับอินพุตทั้งหมด การตั้งค่าแซนด์บ็อกซ์ต้องใช้เวลาเพิ่มเติม โดยเฉพาะใน 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
จะมีลักษณะดังนี้
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 จะแยกวิเคราะห์คําตอบนี้และแปลงเป็นโปรโตคอล WorkResponse
ด้วยตนเอง เพื่อสื่อสารกับผู้ปฏิบัติงานที่เกี่ยวข้องโดยใช้โปรโตบูฟที่เข้ารหัสไบนารีแทน JSON requires-worker-protocol
จะตั้งค่าเป็น proto
ดังนี้
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
หากไม่ได้รวม requires-worker-protocol
ไว้ในข้อกําหนดในการดําเนินการ Bazel จะตั้งค่าเริ่มต้นให้การสื่อสารของพนักงานใช้ protobuf
Bazel จะดึง WorkerKey
จากหน่วยความจําและแฟล็กที่แชร์ ดังนั้นหากการกําหนดค่านี้อนุญาตให้เปลี่ยนพารามิเตอร์ max_mem
ระบบจะรวมผู้ปฏิบัติงานแต่ละคนสําหรับแต่ละค่าที่ใช้ ซึ่งอาจทําให้มีการใช้หน่วยความจํามากเกินไปหากมีการใช้ตัวแปรมากเกินไป
ในตอนนี้ ผู้ปฏิบัติงานแต่ละคนจะดําเนินการกับคําขอได้ทีละคําขอเท่านั้น ฟีเจอร์ผู้ปฏิบัติงาน Multiplex รุ่นทดลองใช้ชุดข้อความได้หลายรายการ หากเครื่องมือสําคัญเป็นชุดข้อความหลายชุดและตั้งค่า Wrapper ให้เข้าใจสิ่งนี้
ในที่เก็บ GitHub นี้ คุณจะเห็นตัวอย่าง Wrapper ของผู้ปฏิบัติงานที่เขียนใน Java รวมถึง Python หากคุณทํางานใน JavaScript หรือ TypeScript แพ็กเกจ@bazel/worker และตัวอย่างผู้ปฏิบัติงานของ nodejs อาจเป็นประโยชน์
ผู้ปฏิบัติงานมีผลต่อแซนด์บ็อกซ์อย่างไร
การใช้กลยุทธ์ worker
โดยค่าเริ่มต้นจะไม่เรียกใช้การดําเนินการในแซนด์บ็อกซ์ ซึ่งคล้ายกับกลยุทธ์ local
คุณตั้งค่าแฟล็ก --worker_sandboxing
เพื่อเรียกใช้ผู้ปฏิบัติงานทั้งหมดภายในแซนด์บ็อกซ์ได้ โดยตรวจสอบให้แน่ใจว่าการทํางานของเครื่องมือแต่ละรายการจะเห็นเฉพาะไฟล์อินพุตตามที่ควรมี เครื่องมือดังกล่าวอาจยังรั่วไหลระหว่างคําขอภายใน เช่น ผ่านแคช การใช้กลยุทธ์ dynamic
กําหนดให้ผู้ปฏิบัติงานต้องแซนด์บ็อกซ์
ระบบจะส่งเนื้อหาไดเจสต์ไปพร้อมกับไฟล์อินพุตแต่ละรายการเพื่อให้ใช้แคชของคอมไพเลอร์ได้อย่างถูกต้อง ดังนั้น คอมไพเลอร์หรือ Wrapper จะตรวจสอบว่าอินพุตยังคงถูกต้องโดยไม่ต้องอ่านไฟล์หรือไม่
แม้จะใช้ไดเจสต์อินพุตเพื่อป้องกันการแคชที่ไม่พึงประสงค์ แซนด์บ็อกซ์แซนด์บ็อกซ์ก็จะช่วยเพิ่มแซนด์บ็อกซ์ที่เข้มงวดน้อยกว่าแซนด์บ็อกซ์แบบปกติ เนื่องจากเครื่องมืออาจเก็บสถานะภายในอื่นๆ ที่ได้รับผลกระทบจากคําขอก่อนหน้า
ผู้ปฏิบัติงานด้าน Multiplex จะสามารถแซนด์บ็อกซ์ได้ต่อเมื่อการดําเนินการของผู้ปฏิบัติงานรองรับเท่านั้น และแซนด์บ็อกซ์นั้นต้องเปิดใช้แยกกันโดยมีแฟล็ก --experimental_worker_multiplex_sandboxing
ดูรายละเอียดเพิ่มเติมในเอกสารการออกแบบ)
อ่านเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ปฏิบัติงานถาวรได้ที่
- บล็อกโพสต์ต้นฉบับของผู้ปฏิบัติงานถาวร
- คําอธิบายการใช้งาน Haskell {: .external}
- บล็อกโพสต์ของ Mike Morearty {: .external}
- การพัฒนาส่วนหน้าด้วย Bazel: Angular/TypeScript และ Persistent Workers กับ Asana {: .external}
- คําอธิบายกลยุทธ์ Bazel {: .external}
- การพูดคุยเกี่ยวกับกลยุทธ์ผู้ปฏิบัติงานโดยใช้ข้อมูลในรายชื่ออีเมลเกี่ยวกับ Bazel-Conversation {: .external}