ความท้าทายของกฎการเขียน

วันที่ รายงานปัญหา ดูแหล่งที่มา ตอนกลางคืน · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

หน้านี้จะแสดงภาพรวมระดับสูงของปัญหาและความท้าทายที่เฉพาะเจาะจง เกี่ยวกับการเขียนกฎ ของ Bazel ที่มีประสิทธิภาพ

ข้อกำหนดในการสรุป

  • สมมติฐาน: มุ่งเน้นไปที่ความถูกต้อง อัตราการส่งข้อมูล การใช้งานง่าย และ เวลาในการตอบสนอง
  • สมมติฐาน: ที่เก็บขนาดใหญ่
  • สมมติฐาน: ภาษาคำอธิบายเหมือนกับ BUILD
  • ประวัติศาสตร์: การแยกอย่างชัดเจนระหว่างการโหลด การวิเคราะห์ และการดำเนินการคือ ล้าสมัยแต่ยังคงส่งผลกระทบต่อ API
  • Intrinsic: การดำเนินการจากระยะไกลและการแคชเป็นเรื่องยาก
  • Intrinsic: การใช้ข้อมูลการเปลี่ยนแปลงเพื่อสร้างส่วนเพิ่มที่ถูกต้องและรวดเร็ว ต้องใช้รูปแบบการเขียนโค้ดที่ผิดปกติ
  • ภายใน: การหลีกเลี่ยงเวลากำลังสองและการใช้หน่วยความจำนั้นเป็นเรื่องยาก

สมมติฐาน

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

มุ่งเน้นความถูกต้อง อัตราการส่งข้อมูล การใช้งานง่าย และ เวลาในการตอบสนอง

เราคิดว่าระบบบิลด์จำเป็นต้องมีความถูกต้องเป็นอย่างแรกและสำคัญที่สุดกับ เกี่ยวกับบิลด์ที่เพิ่มขึ้น สำหรับโครงสร้างแหล่งที่มาหนึ่งๆ เอาต์พุตของ บิลด์เดียวกันควรจะเหมือนกันเสมอ ไม่ว่าเอาต์พุตต้นไม้จะเป็นอย่างไร ชอบ การประมาณครั้งแรกหมายความว่า Bazel จำเป็นต้องทราบ ที่อยู่ในขั้นตอนบิลด์ที่กำหนด เพื่อให้เรียกใช้ขั้นตอนนั้นอีกครั้งได้หากมี อินพุตจะเปลี่ยนไป มีขีดจำกัดว่า Bazel จะได้รับข้อมูลที่ถูกต้องเพียงใด เพราะมีการรั่วไหล ข้อมูลบางอย่าง เช่น วันที่ / เวลาของบิลด์ และละเว้น การเปลี่ยนแปลง เช่น การเปลี่ยนแปลงแอตทริบิวต์ของไฟล์ แซนด์บ็อกซ์ ช่วยให้มั่นใจว่าถูกต้องโดยป้องกันการอ่านไฟล์อินพุตที่ไม่ได้ประกาศ นอกเหนือจาก ข้อจำกัดที่แท้จริงของระบบ มีปัญหาความถูกต้องที่ทราบแล้ว ซึ่งส่วนใหญ่เกี่ยวข้องกับกฎ Fileset หรือ C++ ซึ่งเป็นสิ่งที่ทำได้ยาก ปัญหา เรามีความพยายามในระยะยาวที่จะแก้ไขปัญหานี้

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

มาพร้อมกับความง่ายในการใช้งาน ของวิธีการที่ถูกต้องหลายแนวทาง โดยใช้ (หรือ คล้ายกัน) ของบริการดำเนินการระยะไกล เราจะเลือกรายการที่เป็น ใช้งานง่ายขึ้น

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

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

ที่เก็บขนาดใหญ่

ระบบบิลด์ต้องทำงานตามระดับของที่เก็บขนาดใหญ่ซึ่ง หมายความว่า ขนาดไม่พอดีกับฮาร์ดไดรฟ์หนึ่งๆ ดังนั้นจึงเป็นไปไม่ได้ที่จะ ชำระเงินเต็มจำนวนบนเครื่องสำหรับนักพัฒนาซอฟต์แวร์เกือบทั้งหมด งานสร้างขนาดกลาง จะต้องอ่านและแยกวิเคราะห์ไฟล์ BUILD หลายหมื่นไฟล์ และประเมิน หลายแสนลูกโลก แม้ว่าในทางทฤษฎีจะเป็นไปได้ที่จะอ่านทั้งหมด BUILD ไฟล์ในเครื่องเดียว เรายังไม่สามารถทำเช่นนั้นได้ภายใน ระยะเวลาและหน่วยความจำที่เหมาะสม ด้วยเหตุนี้ ไฟล์ BUILD จึงมีความสำคัญ สามารถโหลดและแยกวิเคราะห์แยกจากกันได้

ภาษาคำอธิบายที่คล้ายกับ BUILD

ในบริบทนี้ เราจะใช้ภาษาการกำหนดค่า คล้ายกับไฟล์ BUILD ในการประกาศกฎไลบรารีและกฎไบนารี และการพึ่งพาอาศัยกัน อ่านและแยกวิเคราะห์ไฟล์ BUILD ไฟล์ได้อย่างอิสระ เราหลีกเลี่ยงการดูไฟล์ต้นฉบับเมื่อไรก็ได้ (ยกเว้น ที่มีอยู่)

ประวัติศาสตร์

Bazel เวอร์ชันต่างๆ ที่ก่อให้เกิดความท้าทายและบางเวอร์ชันนั้นแตกต่างกัน ทั้งหมดจะแสดงอยู่ในส่วนต่อไปนี้

การแยกอย่างชัดเจนระหว่างการโหลด การวิเคราะห์ และการดำเนินการล้าสมัยแล้วแต่ยังคงส่งผลต่อ API

ในทางเทคนิค กฎเพื่อให้ทราบไฟล์อินพุตและเอาต์พุตของ การดำเนินการก่อนที่จะส่งการดำเนินการไปยังการดำเนินการจากระยะไกล อย่างไรก็ตาม ฐานโค้ด Bazel เดิมมีการแบ่งแยกแพ็กเกจการโหลดอย่างเคร่งครัด วิเคราะห์กฎโดยใช้การกำหนดค่า (หลักๆ แล้วแฟล็กบรรทัดคำสั่ง) และ จากนั้นจึงเรียกใช้การดำเนินการต่างๆ เท่านั้น ความแตกต่างนี้ยังคงเป็นส่วนหนึ่งของกฎ API แม้ว่าหัวใจหลักของ Bazel จะไม่จำเป็นต้องใช้อีกต่อไป (รายละเอียดเพิ่มเติมด้านล่าง)

ซึ่งหมายความว่า API ของกฎต้องมีคำอธิบายกฎอย่างชัดเจน ของอินเทอร์เฟซ (แอตทริบิวต์ที่มี ประเภทของแอตทริบิวต์) มีบางส่วน ข้อยกเว้นที่ API จะอนุญาตให้โค้ดที่กำหนดเองทำงานในขั้นตอนการโหลดเพื่อ ชื่อโดยนัยของไฟล์เอาต์พุตและค่าโดยนัยของแอตทริบิวต์ สำหรับ เช่น กฎ java_library ชื่อ "foo" สร้างเอาต์พุตที่ตั้งชื่อโดยปริยาย "libfoo.jar" ซึ่งอ้างอิงจากกฎอื่นๆ ในกราฟบิลด์ได้

นอกจากนี้ การวิเคราะห์กฎจะไม่สามารถอ่านไฟล์ต้นฉบับหรือตรวจสอบ เอาต์พุตของการดำเนินการ แต่ต้องการสร้าง Bipartite ที่มีทิศทางบางส่วนแทน กราฟของขั้นตอนบิลด์และชื่อไฟล์เอาต์พุตที่กำหนดจากกฎเท่านั้น และทรัพยากร Dependency โดยตรง

ภายใน

มีคุณสมบัติเฉพาะบางอย่างที่ทำให้กฎการเขียนเป็นเรื่องที่ท้าทายและ โดยวิธีดำเนินการที่พบบ่อยที่สุดบางส่วนได้อธิบายไว้ในส่วนต่อไปนี้

การดำเนินการและการแคชระยะไกลนั้นทำได้ยาก

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

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

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

ด้านบน เราโต้แย้งว่าเพื่อความถูกต้อง Bazel จำเป็นต้องรู้ข้อมูลทั้งหมด ที่เข้าสู่ขั้นตอนบิลด์เพื่อตรวจสอบว่าขั้นตอนบิลด์นั้น ยังคงทันสมัย เช่นเดียวกันสำหรับการโหลดแพ็กเกจและการวิเคราะห์กฎ และเรา ได้ออกแบบ Skyframe เพื่อจัดการกับเรื่องนี้ โดยทั่วไป Skyframe คือไลบรารีกราฟและกรอบการประเมินที่ โหนดเป้าหมาย (เช่น "สร้าง //foo ด้วยตัวเลือกเหล่านี้") และแบ่งโหนดเป็น ส่วนประกอบต่างๆ ของ Google Analytics ซึ่งจะมีการประเมินและรวมเข้าด้วยกันเพื่อทำให้เกิด ผลลัพธ์ ในขั้นตอนนี้ Skyframe จะอ่านแพ็กเกจ วิเคราะห์กฎ และ ดำเนินการต่างๆ

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

แต่ละโหนดจะดำเนินกระบวนการค้นหาทรัพยากร Dependency ด้วย ชิ้น โหนดสามารถประกาศทรัพยากร Dependency แล้วใช้เนื้อหาของทรัพยากร Dependency เหล่านั้น เพื่อประกาศทรัพยากร Dependency เพิ่มเติม โดยหลักการแล้ว วิธีนี้แมปได้อย่างดีกับ รูปแบบเทรดต่อโหนด อย่างไรก็ตาม งานสร้างขนาดกลางประกอบด้วย โหนด Skyframe หลายพันโหนด ซึ่งทำได้ยากเมื่อใช้ Java ในปัจจุบัน เทคโนโลยี (และด้วยเหตุผลทางประวัติศาสตร์ ปัจจุบันเรายึดมั่นอยู่กับการใช้ Java ดังนั้น ไม่มีชุดข้อความที่เรียบง่ายและไม่มีความต่อเนื่อง)

แต่ Bazel จะใช้ Thread Pool ที่มีขนาดคงที่แทน แต่หมายความว่าถ้าโหนดหนึ่ง ประกาศว่าทรัพยากร Dependency ยังไม่พร้อมใช้งาน เราอาจต้องล้มเลิก การประเมินเริ่มต้น และเริ่มต้นใหม่ (โดยอาจเป็นไปได้ในเทรดอื่น) เมื่อการอ้างอิงคือ พร้อมใช้งาน ซึ่งก็หมายความว่าโหนดต่างๆ ไม่ควรทำงานนี้มากเกินไป CANNOT TRANSLATE โหนดที่ประกาศทรัพยากร Dependency แบบอนุกรมสามารถรีสตาร์ทได้ N ครั้ง ซึ่งทำให้เสียเวลา O(N^2) แต่เราตั้งเป้าที่จะประกาศ ทรัพยากร Dependency ซึ่งบางครั้งก็ต้องมีการจัดระเบียบโค้ดใหม่ หรือแม้แต่การแยก หนึ่งโหนดลงในหลายโหนดเพื่อจำกัดจำนวนการรีสตาร์ท

โปรดทราบว่าเทคโนโลยีนี้ยังไม่พร้อมใช้งานในกฎ API ในขณะนี้ แทน กฎ API ยังคงได้รับการกำหนดโดยใช้แนวคิดเดิมเกี่ยวกับการโหลด การวิเคราะห์ และการดำเนินการ อย่างไรก็ตาม ข้อจำกัดพื้นฐานก็คือการเข้าถึงทั้งหมด โหนดอื่นๆ จะต้องผ่านเฟรมเวิร์ก เพื่อให้ติดตาม ทรัพยากร Dependency ที่เกี่ยวข้อง ไม่ว่าระบบบิลด์จะใช้ภาษาใดก็ตาม ถูกนำไปใช้ หรือมีการระบุกฎ (กฎที่ว่าไม่จำเป็นต้องเป็น เดียวกัน) ผู้เขียนกฎต้องไม่ใช้ไลบรารีมาตรฐานหรือรูปแบบที่ข้าม Skyframe สำหรับ Java หมายถึงการหลีกเลี่ยง java.io.File รวมถึงไฟล์ และไลบรารีใดก็ได้ที่ทำเช่นนั้น ไลบรารีที่รองรับทรัพยากร Dependency ก็ยังต้องตั้งค่าอย่างถูกต้องสำหรับ Skyframe

การดำเนินการนี้ควรหลีกเลี่ยงการเปิดเผยรันไทม์แบบเต็มภาษาแก่ผู้เขียนกฎ ตั้งแต่แรก อันตรายของการใช้ API ดังกล่าวโดยไม่ตั้งใจนั้นมากเกินไป ข้อบกพร่องของ Bazel หลายรายการในอดีตเกิดจากกฎที่ใช้ API ที่ไม่ปลอดภัย แม้ว่ากฎจะเขียนโดยทีม Bazel หรือผู้เชี่ยวชาญด้านโดเมนอื่นๆ

การหลีกเลี่ยงเวลากำลังสองและการใช้หน่วยความจำเป็นเรื่องยาก

เพื่อให้เรื่องเลวร้ายลง นอกเหนือจากข้อกำหนดที่ Skyframe กำหนดแล้ว ข้อจำกัดในอดีตของการใช้ Java และความล้าสมัยของกฎ API การนำเวลากำลังสองหรือการใช้หน่วยความจำไปใช้โดยไม่ตั้งใจเป็นพื้นฐาน ปัญหาในระบบบิลด์ใดๆ ที่อิงตามกฎไลบรารีและไบนารี มี 2 แบบ รูปแบบที่พบได้ทั่วไปซึ่งทำให้เกิดการใช้หน่วยความจำกำลังสอง (ดังนั้น เวลาที่ใช้กำลังสอง)

  1. กลุ่มกฎห้องสมุด - ลองพิจารณากรณีของเชนกฎไลบรารี A ขึ้นอยู่กับ B ขึ้นอยู่กับ C และ เป็นต้น จากนั้น เราต้องการคำนวณทรัพย์สินบางส่วนจากการปิดทางอ้อมของ กฎเหล่านี้ เช่น คลาสพาธรันไทม์ของ Java หรือคำสั่ง C++ Linker สำหรับ ห้องสมุดแต่ละแห่ง อย่างไรก็ดี เราอาจใช้รายการมาตรฐาน อย่างไรก็ตาม มีการใช้หน่วยความจำกำลังสองแล้ว: ไลบรารีแรก มีรายการ 1 รายการในคลาสพาธ รายการที่ 2 รายการที่ 3 และรายการ 3 รายการตามลำดับ เปิด ตามจำนวนรายการ 1+2+3+...+N = O(N^2) รายการ

  2. กฎไบนารีที่ขึ้นอยู่กับกฎไลบรารีเดียวกัน - พิจารณาในกรณีที่ชุดไบนารีที่อิงกับไลบรารีเดียวกัน — เช่น หากคุณมีจำนวนกฎทดสอบที่ทดสอบชุดเดียวกัน รหัสห้องสมุด สมมติว่าจากกฎ N กฎครึ่งหนึ่งเป็นกฎไบนารี และ กฎอีกครึ่งหนึ่งของไลบรารี ทีนี้ลองพิจารณาว่าไบนารีแต่ละชุดทำสำเนา คุณสมบัติบางอย่างที่คำนวณผ่านการปิดทางอ้อมของกฎห้องสมุด เช่น คลาสพาธรันไทม์ของ Java หรือบรรทัดคำสั่ง C++ Linker ตัวอย่างเช่น อาจขยายการแสดงสตริงบรรทัดคำสั่งของการดำเนินการลิงก์ C++ ไม่มี สำเนาขององค์ประกอบ N/2 คือหน่วยความจำ O(N^2)

คลาสคอลเล็กชันที่กำหนดเองเพื่อหลีกเลี่ยงความซับซ้อนของกำลังสอง

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

อย่างไรก็ตาม การใช้ Depset ไม่ได้ช่วยแก้ปัญหาทั้งหมดโดยอัตโนมัติ โดยเฉพาะอย่างยิ่ง แม้กระทั่งการทำซ้ำในช่วงหนึ่งของกฎแต่ละข้อ เวลาที่ใช้กำลังสอง สำหรับภายในองค์กร NestedSets ยังมีวิธีการที่เป็นตัวช่วยบางอย่างอีกด้วย เพื่ออำนวยความสะดวกในการทำงานร่วมกันกับคลาสการเรียกเก็บเงินปกติ ต้องขออภัยด้วย การส่ง NestedSet ไปยังวิธีใดวิธีหนึ่งโดยไม่ได้ตั้งใจจะนำไปสู่การคัดลอก และแสดงการใช้หน่วยความจำกำลังสองอีกครั้ง