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

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

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

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

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