ผู้ปฏิบัติงานถาวร

รายงานปัญหา ดูแหล่งที่มา

หน้านี้จะพูดถึงวิธีใช้ผู้ปฏิบัติงานถาวร ประโยชน์ ข้อกำหนด และวิธีที่ผู้ปฏิบัติงานมีผลต่อแซนด์บ็อกซ์

ผู้ปฏิบัติงานถาวรคือกระบวนการที่มีระยะเวลายาวนานที่เริ่มต้นโดยเซิร์ฟเวอร์ 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 ดูรายละเอียดเพิ่มเติมในเอกสารการออกแบบ)

อ่านเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ปฏิบัติงานถาวรได้ที่