แจกแจงประสิทธิภาพของบิลด์

รายงานปัญหา ดูซอร์สโค้ด รุ่น Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

บิลด์แบบสะอาดกับบิลด์แบบเพิ่ม

บิลด์ที่สะอาดคือบิลด์ที่สร้างทุกอย่างตั้งแต่ต้น ส่วนบิลด์แบบเพิ่มจะนํางานที่ทำเสร็จแล้วบางส่วนมาใช้ซ้ำ

เราขอแนะนำให้ดูบิลด์ที่สะอาดและบิลด์ที่เพิ่มเข้ามาแยกกัน โดยเฉพาะเมื่อคุณรวบรวม / รวบรวมเมตริกที่ขึ้นอยู่กับสถานะของแคชของ Bazel (เช่น เมตริกขนาดคำขอบิลด์) นอกจากนี้ บิลด์เหล่านี้ยังแสดงถึงประสบการณ์การใช้งานที่แตกต่างกัน 2 แบบ เมื่อเทียบกับการเริ่มสร้างใหม่ตั้งแต่ต้น (ซึ่งใช้เวลานานกว่าเนื่องจากแคชเย็น) การสร้างแบบเพิ่มจะทําได้บ่อยกว่ามากเมื่อนักพัฒนาซอฟต์แวร์แก้ไขโค้ดซ้ำๆ (โดยปกติจะเร็วกว่าเนื่องจากแคชมักจะอุ่นอยู่แล้ว)

คุณสามารถใช้ช่อง CumulativeMetrics.num_analyses ใน BEP เพื่อจัดประเภทบิลด์ได้ หากเป็น num_analyses <= 1 แสดงว่าเป็นบิลด์ที่สะอาด หากไม่ใช่ เราอาจจัดหมวดหมู่อย่างคร่าวๆ ว่าน่าจะเป็นบิลด์ที่เพิ่มเข้ามา เนื่องจากผู้ใช้อาจเปลี่ยนไปใช้ Flag หรือเป้าหมายอื่นซึ่งทำให้บิลด์สะอาด คำจำกัดความที่เข้มงวดยิ่งขึ้นของการเพิ่มจำนวนมีแนวโน้มที่จะอยู่ในรูปแบบของการเฮิวริสติก เช่น การดูจำนวนแพ็กเกจที่โหลด (PackageMetrics.packages_loaded)

เมตริกการสร้างแบบกำหนดได้เพื่อใช้แทนประสิทธิภาพการสร้าง

การวัดประสิทธิภาพการสร้างอาจเป็นเรื่องยากเนื่องจากเมตริกบางรายการมีลักษณะที่ไม่แน่นอน (เช่น เวลา CPU ของ Bazel หรือเวลาคิวในคลัสเตอร์ระยะไกล) ดังนั้นจึงอาจเป็นประโยชน์ในการใช้เมตริกแบบกำหนดได้เพื่อประเมินปริมาณงานที่ Bazel ดำเนินการ ซึ่งจะส่งผลต่อประสิทธิภาพของ Bazel

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

เราแบ่งปัญหานี้ออกเป็นระยะต่างๆ ของการสร้างได้ และใช้เมตริกต่อไปนี้เป็นเมตริกพร็อกซีสําหรับงานที่ทําในแต่ละระยะ

  1. PackageMetrics.packages_loaded: จํานวนแพ็กเกจที่โหลดสําเร็จ การถดถอยที่นี่แสดงถึงงานเพิ่มเติมที่ต้องทำเพื่ออ่านและแยกวิเคราะห์ไฟล์ BUILD แต่ละไฟล์เพิ่มเติมในระยะการโหลด

    • ซึ่งมักเกิดจากการเพิ่มการพึ่งพาและต้องโหลดการปิดเชิงซ้อน
    • ใช้ query / cquery เพื่อค้นหาว่ามีการเพิ่มการพึ่งพาใหม่ไว้ที่ใด
  2. TargetMetrics.targets_configured: แสดงจํานวนเป้าหมายและด้านต่างๆ ที่กําหนดค่าไว้ในบิลด์ การถดถอยแสดงถึงงานเพิ่มเติมในการสร้างและเรียกดูกราฟเป้าหมายที่กําหนดค่าไว้

    • ซึ่งมักเกิดจากการเพิ่มการพึ่งพาและต้องสร้างกราฟของ Closure แบบทรานซิทีฟ
    • ใช้ cquery เพื่อค้นหาตำแหน่งที่อาจมีการเพิ่มข้อกำหนดใหม่
  3. ActionSummary.actions_created: แสดงการดำเนินการที่สร้างขึ้นในบิลด์ และการแสดงภาพเป็นภาพกราฟแสดงการทำงานเพิ่มเติมในการสร้างกราฟการดำเนินการ โปรดทราบว่าการดำเนินการนี้ยังรวมถึงการดำเนินการที่ไม่ได้ใช้ซึ่งอาจไม่ได้ดำเนินการด้วย

    • ใช้ aquery สำหรับการแก้ไขข้อบกพร่องแบบย้อนกลับ เราขอแนะนำให้เริ่มต้นด้วย --output=summary ก่อนเจาะลึกด้วย --skyframe_state
  4. ActionSummary.actions_executed: จํานวนการดําเนินการ ภาวะถดถอยแสดงถึงงานเพิ่มเติมในการดําเนินการเหล่านี้โดยตรง

    • BEP จะเขียนสถิติการดําเนินการ ActionData ออกมา ซึ่งแสดงประเภทการดําเนินการที่ดําเนินการมากที่สุด โดยค่าเริ่มต้น ระบบจะรวบรวมประเภทการดําเนินการ 20 อันดับแรก แต่คุณสามารถส่ง --experimental_record_metrics_for_all_mnemonics เพื่อรวบรวมข้อมูลนี้สําหรับประเภทการดําเนินการทั้งหมดที่ดำเนินการ
    • ซึ่งจะช่วยให้คุณทราบว่ามีการดำเนินการประเภทใดบ้าง (เพิ่มเติม)
  5. BuildGraphSummary.outputArtifactCount: จํานวนรายการต่างๆ ที่สร้างขึ้นจากการดำเนินการที่ดำเนินการ

    • หากจํานวนการดําเนินการที่ดำเนินการไม่ได้เพิ่มขึ้น แสดงว่าอาจมีการเปลี่ยนแปลงการใช้งานกฎ

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

เราพบว่าการถดถอยของเมตริกใดเมตริกหนึ่งเหล่านี้อาจมาพร้อมกับการถดถอยของเวลาจริง เวลาที่ใช้ CPU และการใช้หน่วยความจำ

การใช้ทรัพยากรในเครื่อง

Bazel ใช้ทรัพยากรต่างๆ ในเครื่องของคุณ (ทั้งสําหรับการวิเคราะห์กราฟการบิลด์และขับเคลื่อนการดําเนินการ รวมถึงสําหรับการดําเนินการในเครื่อง) ซึ่งอาจส่งผลต่อประสิทธิภาพ / ความพร้อมใช้งานของเครื่องในการบิลด์ รวมถึงงานอื่นๆ

เวลาที่ใช้

เมตริกที่อาจไวต่อสัญญาณรบกวนมากที่สุด (และอาจแตกต่างกันอย่างมากจากบิลด์หนึ่งๆ ไปอีกบิลด์หนึ่ง) ก็คือเวลา โดยเฉพาะเวลาจริง เวลา CPU และเวลาของระบบ คุณสามารถใช้ bazel-bench เพื่อดูการเปรียบเทียบสำหรับเมตริกเหล่านี้ และหากมี --runs เพียงพอ คุณจะเพิ่มความสําคัญทางสถิติของการวัดได้

  • เวลาจริงคือเวลาที่ผ่านไปในชีวิตจริง

    • หากเฉพาะเวลาจริงที่ลดลง เราขอแนะนำให้รวบรวมโปรไฟล์การติดตาม JSON และมองหาความแตกต่าง หรืออาจมีประสิทธิภาพมากกว่าหากตรวจสอบเมตริกอื่นๆ ที่ลดลง เนื่องจากอาจส่งผลต่อเวลาในการรับชม
  • เวลา CPU คือเวลาที่ CPU ใช้ในการเรียกใช้โค้ดของผู้ใช้

    • หากเวลา CPU ลดลงในคอมมิต 2 รายการของโปรเจ็กต์ เราขอแนะนำให้รวบรวมโปรไฟล์ CPU ของ Starlark คุณควรใช้ --nobuild เพื่อจำกัดการสร้างให้อยู่ในระยะการวิเคราะห์ด้วย เนื่องจากเป็นระยะที่ CPU ทำงานหนักมากที่สุด
  • เวลาของระบบคือเวลาที่ CPU ใช้ในเคอร์เนล

    • หากเวลาของระบบลดลง แสดงว่าเวลาดังกล่าวส่วนใหญ่เกี่ยวข้องกับ I/O เมื่อ Bazel อ่านไฟล์จากระบบไฟล์

โปรไฟล์การโหลดทั่วทั้งระบบ

เมื่อใช้ตัวเลือก --experimental_collect_load_average_in_profiler ที่เปิดตัวใน Bazel 6.0 เครื่องมือวิเคราะห์ร่องรอย JSON จะรวบรวมค่าเฉลี่ยภาระของระบบระหว่างการเรียกใช้

โปรไฟล์ที่มีค่าเฉลี่ยของการโหลดระบบ

รูปที่ 1 โปรไฟล์ที่มีค่าเฉลี่ยของการโหลดระบบ

ภาระงานสูงระหว่างการเรียกใช้ Bazel อาจบ่งชี้ว่า Bazel กำหนดเวลาการดำเนินการในเครื่องพร้อมกันมากเกินไป คุณอาจต้องพิจารณาเกี่ยวกับการปรับ --local_cpu_resources และ --local_ram_resources โดยเฉพาะอย่างยิ่งในสภาพแวดล้อมคอนเทนเนอร์ (อย่างน้อยจนกว่าจะมีการผสาน #16512)

การตรวจสอบการใช้หน่วยความจำของ Bazel

แหล่งที่มาหลักๆ 2 แห่งสําหรับดูการใช้งานหน่วยความจําของ Bazel คือ Bazel info และ BEP

  • bazel info used-heap-size-after-gc: ปริมาณหน่วยความจําที่ใช้เป็นไบต์หลังจากการเรียกใช้ System.gc()

    • Bazel bench มีข้อมูลเปรียบเทียบสำหรับเมตริกนี้ด้วย
    • นอกจากนี้ ยังมี peak-heap-size, max-heap-size, used-heap-size และ committed-heap-size (ดูเอกสารประกอบ) แต่มีความเกี่ยวข้องน้อยกว่า
  • MemoryMetrics.peak_post_gc_heap_size ของ BEP: ขนาดของฮีป JVM สูงสุดเป็นไบต์หลัง GC (กำหนดการตั้งค่า --memory_profile ที่พยายามบังคับให้ GC แบบสมบูรณ์)

การใช้งานหน่วยความจําที่ลดลงมักจะเป็นผลมาจากเมตริกขนาดคําขอบิลด์ที่ลดลง ซึ่งมักเกิดจากการเพิ่มข้อกําหนดหรือการเปลี่ยนแปลงการใช้งานกฎ

หากต้องการวิเคราะห์พื้นที่หน่วยความจำของ Bazel ในรายละเอียดยิ่งขึ้น เราขอแนะนำให้ใช้เครื่องมือวิเคราะห์หน่วยความจำในตัวสำหรับกฎ

การสร้างโปรไฟล์หน่วยความจําของผู้ปฏิบัติงานแบบถาวร

แม้ว่าเวิร์กเกอร์แบบถาวรจะช่วยเร่งความเร็วการสร้างได้อย่างมาก (โดยเฉพาะสำหรับภาษาที่แปล) แต่การใช้หน่วยความจำก็อาจทำให้เกิดปัญหาได้ Bazel จะรวบรวมเมตริกเกี่ยวกับผู้ปฏิบัติงาน โดยเฉพาะอย่างยิ่ง ช่อง WorkerMetrics.WorkerStats.worker_memory_in_kb จะบอกปริมาณหน่วยความจำที่ผู้ปฏิบัติงานใช้ (ตามคําช่วยจํา)

นอกจากนี้ เครื่องมือวิเคราะห์ร่องรอย JSON ยังรวบรวมการใช้งานหน่วยความจำของผู้ปฏิบัติงานแบบถาวรในระหว่างการเรียกใช้ด้วยการส่ง Flag --experimental_collect_system_network_usage (ใหม่ใน Bazel 6.0)

โปรไฟล์ที่มีการใช้งานหน่วยความจำของผู้ปฏิบัติงาน

รูปที่ 2 โปรไฟล์ที่มีการใช้งานหน่วยความจำของผู้ปฏิบัติงาน

การลดค่าของ --worker_max_instances (ค่าเริ่มต้น 4) อาจช่วยในการลดปริมาณหน่วยความจําที่ผู้ปฏิบัติงานแบบถาวรใช้ เรากําลังทํางานอย่างเต็มที่เพื่อทำให้เครื่องมือจัดการทรัพยากรและเครื่องมือจัดตารางเวลาของ Bazel ฉลาดขึ้น เพื่อที่จะได้ไม่ต้องปรับแต่งแบบละเอียดบ่อยนักในอนาคต

การตรวจสอบการรับส่งข้อมูลในเครือข่ายสําหรับบิลด์ระยะไกล

ในการดําเนินการจากระยะไกล Bazel จะดาวน์โหลดอาร์ติแฟกต์ที่สร้างขึ้นจากการดำเนินการ แบนด์วิดท์ของเครือข่ายจึงอาจส่งผลต่อประสิทธิภาพของบิลด์

หากใช้การเรียกใช้จากระยะไกลสําหรับบิลด์ คุณอาจต้องพิจารณาตรวจสอบการเข้าชมเครือข่ายระหว่างการเรียกใช้โดยใช้ NetworkMetrics.SystemNetworkStats proto จาก BEP (ต้องส่ง --experimental_collect_system_network_usage)

นอกจากนี้ โปรไฟล์การติดตาม JSON ยังช่วยให้คุณดูการใช้งานเครือข่ายทั้งระบบตลอดกระบวนการสร้างได้ด้วยการส่ง Flag --experimental_collect_system_network_usage (ใหม่ใน Bazel 6.0)

โปรไฟล์ที่มีการใช้งานเครือข่ายทั้งระบบ

รูปที่ 3 โปรไฟล์ที่มีการใช้งานเครือข่ายทั้งระบบ

การใช้เครือข่ายที่สูงแต่ค่อนข้างคงที่เมื่อใช้การดําเนินการจากระยะไกลอาจบ่งชี้ว่าเครือข่ายเป็นคอขวดในการสร้าง หากยังไม่ได้ใช้ ให้ลองเปิด "สร้างโดยไม่ใช้ Bytes" โดยส่ง --remote_download_minimal ซึ่งจะช่วยเร่งความเร็วการสร้างโดยหลีกเลี่ยงการดาวน์โหลดอาร์ติแฟกต์ระดับกลางที่ไม่จำเป็น

อีกตัวเลือกหนึ่งคือการกำหนดค่าแคชดิสก์ในเครื่องเพื่อประหยัดแบนด์วิดท์การดาวน์โหลด