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

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

เวิร์กเกอร์แบบถาวรคือกระบวนการที่ทำงานต่อเนื่องซึ่งเซิร์ฟเวอร์ Bazel เริ่มต้นขึ้น ซึ่งทำหน้าที่เป็นตัวแฝงรอบเครื่องมือจริง (โดยทั่วไปคือคอมไพเลอร์) หรือเป็นเครื่องมือนั้นเอง เครื่องมือต้องรองรับการคอมไพล์ตามลำดับ และรัปเปอร์ต้องแปลระหว่าง API ของเครื่องมือกับรูปแบบคำขอ/คำตอบที่อธิบายไว้ด้านล่าง เพื่อให้ได้รับประโยชน์จากเวิร์กเกอร์แบบถาวร อาจมีเรียกใช้ Worker เดียวกันโดยใส่หรือไม่ใส่ Flag --persistent_worker ในบิลด์เดียวกัน และมีหน้าที่รับผิดชอบในการเริ่มต้นและพูดคุยกับเครื่องมืออย่างเหมาะสม รวมถึงปิด Worker เมื่อออก ระบบจะกำหนดอินสแตนซ์ของ W worker แต่ละรายการ (แต่ไม่ได้เปลี่ยนเส้นทางไปยัง) ไดเรกทอรีการทำงานแยกต่างหากภายใต้ <outputBase>/bazel-workers

การใช้เวิร์กเกอร์แบบถาวรเป็นกลยุทธ์การดําเนินการที่ช่วยลดค่าใช้จ่ายในการเริ่มต้น อนุญาตให้คอมไพล์ JIT ได้มากขึ้น และเปิดใช้การแคช เช่น ต้นไม้ไวยากรณ์นามธรรมในการดําเนินการ กลยุทธ์นี้ช่วยปรับปรุงประสิทธิภาพโดยการส่งคำขอหลายรายการไปยังกระบวนการที่ทำงานต่อเนื่อง

มีการใช้งานผู้ปฏิบัติงานถาวรสำหรับหลายภาษา ซึ่งรวมถึง Java, Scala, Kotlin และอื่นๆ

โปรแกรมที่ใช้รันไทม์ NodeJS สามารถใช้ไลบรารีตัวช่วย @bazel/worker เพื่อติดตั้งใช้งานโปรโตคอลสำหรับแรงงาน

การใช้ผู้ปฏิบัติงานแบบถาวร

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

bazel build //my:target --strategy=Javac=worker,local

การใช้กลยุทธ์สำหรับโหนดทำงานแทนกลยุทธ์ในเครื่องสามารถเพิ่มความเร็วในการคอมไพล์ได้อย่างมาก ทั้งนี้ขึ้นอยู่กับการใช้งาน สำหรับ Java การสร้างจะเร็วขึ้น 2-4 เท่า และบางครั้งอาจเร็วกว่านั้นสำหรับการคอมไพล์แบบเพิ่ม การคอมไพล์ Bazel นั้น รวดเร็วกว่าประมาณ 2.5 เท่าสำหรับพนักงาน ดูรายละเอียดเพิ่มเติมได้ที่ส่วน "การเลือกจํานวนคนงาน"

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

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

จำนวนอินสแตนซ์เริ่มต้นของผู้ปฏิบัติงานต่อความทรงจำคือ 4 แต่ปรับได้ด้วยแฟล็ก worker_max_instances การใช้ CPU ที่มีอยู่อย่างคุ้มค่าและจำนวนการคอมไพล์ JIT และการตีคู่แคชที่คุณได้รับนั้นมีความเกี่ยวข้องกัน เมื่อมีผู้ปฏิบัติงานมากขึ้น เป้าหมายมากขึ้นก็จะจ่ายต้นทุนในการเริ่มต้นใช้งานโค้ดที่ไม่ใช่ JITT และการพบ Cold Cache หากคุณมีเป้าหมายที่จะสร้างจำนวนน้อย ผู้ปฏิบัติงานคนเดียวอาจให้ผลดีระหว่างความเร็วในการคอมไพล์กับการใช้งานทรัพยากร (ตัวอย่างเช่น ดูปัญหา #8586 แฟล็ก worker_max_instances จะกำหนดจำนวนอินสแตนซ์ผู้ปฏิบัติงานสูงสุดต่อ Mnemonic และ Flag ที่ตั้งค่า (ดูด้านล่าง) ดังนั้นในระบบผสม การใช้หน่วยความจำได้ค่อนข้างมากหากเก็บค่าเริ่มต้นไว้ สำหรับบิลด์ที่เพิ่มขึ้นเรื่อยๆ ประโยชน์ของอินสแตนซ์ผู้ปฏิบัติงานหลายรายการจะยิ่งน้อยลง

กราฟนี้แสดงเวลาคอมไพล์ Bazel (เป้าหมาย //src:bazel) ตั้งแต่ต้นในเวิร์กสเตชัน Linux ที่ใช้ Intel Xeon 3.5 GHz แบบ 6 คอร์ที่มี Hyper-Threading และ 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 เท่านั้น คุณตั้งค่า Flag ของ WOrker ได้เพียง 1 รายการต่อการใช้ Flag นี้ และสำหรับคําช่วยจํารายการเดียวเท่านั้น ระบบไม่ได้สร้างผู้ปฏิบัติงานแยกกันสำหรับคําช่วยจําแต่ละรายการเท่านั้น แต่ยังสร้างสําหรับรูปแบบต่างๆ ของ Flag การเริ่มต้นด้วย ชุดค่าผสมของ mnemonic และ flag เริ่มต้นแต่ละชุดจะรวมกันเป็น WorkerKey และระบบอาจสร้างผู้ปฏิบัติงานได้สูงสุด worker_max_instances คนสำหรับ WorkerKey แต่ละรายการ โปรดดูส่วนถัดไปเกี่ยวกับวิธีที่การกำหนดค่าการดำเนินการสามารถระบุแฟล็กการตั้งค่า

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

การส่งผ่าน Flag --worker_sandboxing จะทำให้คำขอของเวิร์กเกอร์แต่ละรายการใช้ไดเรกทอรีแซนด์บ็อกซ์แยกต่างหากสำหรับอินพุตทั้งหมด การตั้งค่า Sandbox จะใช้เวลาเพิ่มเติมอีกสักหน่อย โดยเฉพาะใน macOS แต่จะให้การรับประกันความถูกต้องที่ดีขึ้น

Flag --worker_quit_after_build มีประโยชน์สําหรับการแก้ไขข้อบกพร่องและการสร้างโปรไฟล์เป็นหลัก Flag นี้จะบังคับให้เวิร์กเกอร์ทั้งหมดหยุดทำงานเมื่อบิวด์เสร็จแล้ว นอกจากนี้ คุณยังส่ง --worker_verbose เพื่อรับเอาต์พุตเพิ่มเติมเกี่ยวกับสิ่งที่ผู้ดําเนินการทําอยู่ได้ด้วย แฟล็กนี้จะแสดงในฟิลด์ verbosity ใน WorkRequest ซึ่งช่วยให้การติดตั้งใช้งาน Executor แสดงผลได้ละเอียดยิ่งขึ้นด้วย

ผู้ปฏิบัติงานจะจัดเก็บบันทึกไว้ในไดเรกทอรี <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 จะใช้ "รูปแบบ Camel Case" (requestId) ในเอกสารนี้ เราจะใช้รูปแบบ Camel Case ในตัวอย่าง JSON แต่จะใช้รูปแบบ Snake Case เมื่อพูดถึงช่อง ไม่ว่าจะใช้โปรโตคอลใดก็ตาม

{
  "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 แยกวิเคราะห์คำตอบนี้และแปลงเป็น Pro ของ WorkResponse ด้วยตนเอง หากต้องการสื่อสารกับผู้ปฏิบัติงานที่เกี่ยวข้องโดยใช้ Protobuf ที่เข้ารหัสแบบไบนารีแทน JSON ให้ตั้งค่า requires-worker-protocol เป็น proto ดังนี้

  execution_requirements = {
    "supports-workers" : "1" ,
    "requires-worker-protocol" : "proto"
  }

หากคุณไม่ได้รวม requires-worker-protocol ไว้ในข้อกำหนดการเรียกใช้ ระบบของ Bazel จะกำหนดค่าเริ่มต้นให้การสื่อสารของเวิร์กเกอร์ใช้ protobuf

Bazel จะดึงข้อมูล WorkerKey จากคําช่วยจําและ Flag ที่แชร์ ดังนั้นหากการกําหนดค่านี้อนุญาตให้เปลี่ยนพารามิเตอร์ max_mem ระบบจะสร้างเวิร์กเกอร์แยกต่างหากสําหรับแต่ละค่าที่ใช้ ซึ่งอาจทําให้หน่วยความจําถูกใช้มากเกินไปหากใช้ตัวแปรมากเกินไป

ปัจจุบันผู้ปฏิบัติงานแต่ละคนจะประมวลผลคำขอได้ครั้งละ 1 รายการเท่านั้น ฟีเจอร์ผู้ปฏิบัติงานมัลติเพล็กซ์รุ่นทดลองอนุญาตให้ใช้ชุดข้อความหลายรายการได้ หากเครื่องมือที่สำคัญเป็นแบบมัลติเทรดและมีการตั้งค่า Wrapper ให้ทำความเข้าใจเรื่องนี้

ในที่เก็บ GitHub นี้ คุณจะเห็นตัวอย่าง Wrapper ของ Worker ที่เขียนด้วย Java และ Python หากใช้งาน JavaScript หรือ TypeScript อยู่ แพ็กเกจ@bazel/worker และตัวอย่างผู้ปฏิบัติงาน Nodejs อาจเป็นประโยชน์

ผู้ปฏิบัติงานส่งผลต่อแซนด์บ็อกซ์อย่างไร

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

ระบบจะส่งข้อมูลสรุปไปพร้อมกับไฟล์อินพุตแต่ละไฟล์เพื่อให้ใช้แคชคอมไพเลอร์กับเวิร์กเกอร์ได้อย่างถูกต้อง ดังนั้นคอมไพเลอร์หรือรัปเปอร์จึงตรวจสอบได้ว่าอินพุตยังคงใช้งานได้หรือไม่โดยไม่ต้องอ่านไฟล์

แม้จะใช้ข้อมูลสรุปอินพุตเพื่อป้องกันการแคชที่ไม่ต้องการ แต่เวิร์กเกอร์ที่อยู่ในแซนด์บ็อกซ์จะให้บริการแซนด์บ็อกซ์ที่เข้มงวดน้อยกว่าแซนด์บ็อกซ์แบบสมบูรณ์ เนื่องจากเครื่องมืออาจเก็บสถานะภายในอื่นๆ ที่ได้รับผลกระทบจากคําขอก่อนหน้าไว้

คุณจะจัดเก็บเวิร์กเกอร์แบบหลายรายการไว้ในแซนด์บ็อกซ์ได้ก็ต่อเมื่อการติดตั้งใช้งานเวิร์กเกอร์รองรับเท่านั้น และจะต้องเปิดใช้แซนด์บ็อกซ์นี้แยกต่างหากด้วย Flag --experimental_worker_multiplex_sandboxing ดูรายละเอียดเพิ่มเติมได้ในเอกสารการออกแบบ)

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

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