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

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

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

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

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