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

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

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

ผู้ปฏิบัติงานถาวรเป็นกระบวนการที่ใช้เวลานานซึ่งเริ่มต้นโดยเซิร์ฟเวอร์ Bazel ซึ่งทำหน้าที่เป็น Wrapper ของเครื่องมือจริง (โดยปกติคือคอมไพเลอร์) หรือก็คือเครื่องมือเอง เพื่อรับประโยชน์จากผู้ปฏิบัติงานอย่างต่อเนื่อง เครื่องมือนี้ต้องรองรับการคอมไพล์ตามลำดับ และ Wrapper จะต้องแปลระหว่าง API ของเครื่องมือกับรูปแบบคำขอ/คำตอบที่อธิบายไว้ด้านล่าง อาจมีการเรียกผู้ปฏิบัติงานคนเดิมโดยอาจมีและไม่มี Flag --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 เท่าสำหรับพนักงาน โปรดดูรายละเอียดเพิ่มเติมที่ส่วน "การเลือกจำนวนผู้ปฏิบัติงาน"

หากมีสภาพแวดล้อมบิลด์ระยะไกลที่ตรงกับสภาพแวดล้อมบิลด์ในเครื่องด้วย คุณก็สามารถใช้กลยุทธ์ dynamic เวอร์ชันทดลอง ซึ่งจะแข่งกับการดำเนินการระยะไกลและการดำเนินการของผู้ปฏิบัติงาน หากต้องการเปิดใช้กลยุทธ์แบบไดนามิก ให้ส่งค่าสถานะ --experimental_spawn_scheduler กลยุทธ์นี้จะเปิดใช้ผู้ปฏิบัติงานโดยอัตโนมัติ คุณจึงไม่ต้องระบุกลยุทธ์ worker แต่คุณยังคงใช้ local หรือ sandboxed เป็นวิดีโอสำรองได้

การเลือกจำนวนผู้ปฏิบัติงาน

จำนวนอินสแตนซ์เริ่มต้นของผู้ปฏิบัติงานต่อ Mnemonic คือ 4 แต่ปรับได้ด้วยแฟล็ก worker_max_instances การใช้งาน CPU ที่มีและปริมาณการคอมไพล์ JIT และ Hit แคชที่คุณได้รับมีข้อดีและข้อเสีย เมื่อมีผู้ปฏิบัติงานมากขึ้น เป้าหมายจำนวนมากขึ้นก็จะจ่ายค่าใช้จ่ายในการเริ่มต้นใช้โค้ดที่ไม่มี JITT และพบ Cold Cache หากคุณมีเป้าหมายที่จะสร้างจำนวนน้อย ผู้ปฏิบัติงานคนเดียวอาจให้ผลดีระหว่างความเร็วในการคอมไพล์กับการใช้งานทรัพยากร (ตัวอย่างเช่น ดูปัญหา #8586 แฟล็ก worker_max_instances จะกำหนดจำนวนอินสแตนซ์ผู้ปฏิบัติงานสูงสุดต่อ Mnemonic และ 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 เพื่อระบุแฟล็กการเริ่มต้นไปยังผู้ปฏิบัติงานได้ ซึ่งคีย์ตามความสามารถในการจำ ตัวอย่างเช่น การส่ง --worker_extra_flag=javac=--debug จะเปิดการแก้ไขข้อบกพร่องสำหรับ Javac เท่านั้น สามารถตั้งค่าแฟล็กผู้ปฏิบัติงานได้เพียง 1 รายการต่อการใช้แฟล็กนี้ และสำหรับ Mnemonic ได้รายการเดียวเท่านั้น ผู้ปฏิบัติงานไม่ได้สร้างขึ้นแยกกันสำหรับ Mnemonic แต่ละรายการ แต่รวมถึงตัวแปรต่างๆ ใน Flag เริ่มต้นใช้งานด้วย ระบบจะรวมการแจ้งเตือนหน่วยความจำและการเริ่มต้นใช้งานแต่ละรายการเข้าด้วยกันเป็น WorkerKey และอาจสร้างผู้ปฏิบัติงาน WorkerKey ได้สูงสุด worker_max_instances ราย โปรดดูส่วนถัดไปเกี่ยวกับวิธีที่การกำหนดค่าการดำเนินการสามารถระบุแฟล็กการตั้งค่า

คุณใช้แฟล็ก --high_priority_workers เพื่อระบุการช่วยจำที่ควรเรียกใช้ตามการจำที่มีลำดับความสำคัญปกติได้ ซึ่งจะช่วยจัดลำดับความสำคัญของการดำเนินการที่อยู่ในเส้นทางวิกฤติอยู่เสมอ หากมีผู้ปฏิบัติงานที่มีลำดับความสำคัญสูงตั้งแต่ 2 คนขึ้นไปที่ดำเนินการตามคำขอ ผู้ปฏิบัติงานอื่นๆ ทั้งหมดจะไม่สามารถทำงานได้ แฟล็กนี้สามารถใช้ได้หลายครั้ง

การส่งผ่าน Flag --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 จะมีลักษณะดังนี้

หมายเหตุ: แม้ว่าข้อกำหนดบัฟเฟอร์โปรโตคอลจะใช้ "รูปแบบงู" (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 จะกำหนดค่าเริ่มต้นการสื่อสารสำหรับผู้ปฏิบัติงานให้ใช้ 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 ดูรายละเอียดเพิ่มเติมได้ในเอกสารการออกแบบ)

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

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