สกายเฟรม

โมเดลการประเมินแบบขนานและส่วนเพิ่มของ Bazel

โมเดลข้อมูล

โมเดลข้อมูลประกอบด้วยรายการต่อไปนี้

  • SkyValue หรือที่เรียกว่าโหนด SkyValues เป็นออบเจ็กต์ที่ไม่เปลี่ยนแปลงซึ่งมีข้อมูลทั้งหมดที่สร้างขึ้นระหว่างการบิลด์และอินพุตของการบิลด์ ตัวอย่างเช่น ไฟล์อินพุต ไฟล์เอาต์พุต เป้าหมาย และเป้าหมายที่กำหนดค่า
  • SkyKey ชื่อสั้นๆ ที่ไม่เปลี่ยนแปลงเพื่ออ้างอิง SkyValue เช่น FILECONTENTS:/tmp/foo หรือ PACKAGE://foo
  • SkyFunction สร้างโหนดตามคีย์และโหนดที่ขึ้นต่อกัน
  • กราฟโหนด โครงสร้างข้อมูลที่มีความสัมพันธ์แบบขึ้นต่อกันระหว่างโหนด
  • Skyframeชื่อเวอร์ชันของเฟรมเวิร์กการประเมินแบบเพิ่มที่ Bazel ใช้

การประเมิน

การบิลด์จะทำได้โดยการประเมินโหนดที่แสดงคำขอการบิลด์

ขั้นแรก Bazel จะค้นหา SkyFunction ที่สอดคล้องกับคีย์ของ SkyKey ระดับบนสุด จากนั้นฟังก์ชันจะขอให้ประเมินโหนดที่ต้องใช้ในการประเมินโหนดระดับบนสุด ซึ่งจะส่งผลให้มีการเรียก SkyFunction อื่นๆ จนกว่าจะถึงโหนดใบ โดยปกติแล้วโหนดใบจะเป็นโหนดที่แสดงไฟล์อินพุตในระบบไฟล์ สุดท้าย Bazel จะได้ค่าของ SkyValue ระดับบนสุด ผลข้างเคียงบางอย่าง (เช่น ไฟล์เอาต์พุตในระบบไฟล์) และกราฟแบบมีทิศทางและไม่มีวงจรของการขึ้นต่อกันระหว่างโหนดที่เกี่ยวข้องกับการบิลด์

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

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

  • ขอให้ประเมินโหนดอื่นโดยเรียก env.getValue หากโหนดพร้อมใช้งาน ระบบจะแสดงผลค่าของโหนดนั้น ไม่เช่นนั้นระบบจะแสดงผล null และฟังก์ชันเองก็คาดว่าจะแสดงผล null ในกรณีหลัง ระบบจะประเมินโหนดที่ขึ้นต่อกัน จากนั้นจะเรียกใช้ตัวสร้างโหนดเดิมอีกครั้ง แต่คราวนี้การเรียก env.getValue เดียวกันจะแสดงผลค่าที่ไม่ใช่ null
  • ขอให้ประเมินโหนดอื่นๆ หลายโหนดโดยเรียก env.getValues() การดำเนินการนี้จะทำสิ่งเดียวกันโดยพื้นฐาน ยกเว้นว่าระบบจะประเมินโหนดที่ขึ้นต่อกันแบบขนาน
  • ทำการคำนวณระหว่างการเรียกใช้
  • มีผลข้างเคียง เช่น การเขียนไฟล์ลงในระบบไฟล์ ต้องระมัดระวังไม่ให้ฟังก์ชัน 2 ฟังก์ชันที่แตกต่างกันรบกวนการทำงานของกันและกัน โดยทั่วไป ผลข้างเคียงจากการเขียน (ที่โฟลว์ข้อมูลไหลออกจาก Bazel) จะใช้ได้ แต่ผลข้างเคียงจากการอ่าน (ที่โฟลว์ข้อมูลไหลเข้าสู่ Bazel โดยไม่มีทรัพยากร Dependency ที่ลงทะเบียนไว้) จะใช้ไม่ได้ เนื่องจากเป็นการขึ้นต่อกันที่ไม่ได้ลงทะเบียนไว้และอาจทำให้เกิดการบิลด์แบบเพิ่มที่ไม่ถูกต้อง

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

เมื่อฟังก์ชันมีข้อมูลเพียงพอที่จะทำงานได้แล้ว ฟังก์ชันควรแสดงผลค่าที่ไม่ใช่ null เพื่อระบุว่าการทำงานเสร็จสมบูรณ์

กลยุทธ์การประเมินนี้มีประโยชน์หลายประการ ได้แก่

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

ส่วนเพิ่ม

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

โดยเฉพาะอย่างยิ่ง กลยุทธ์การเพิ่มประสิทธิภาพที่เป็นไปได้มี 2 กลยุทธ์ ได้แก่ กลยุทธ์จากล่างขึ้นบนและกลยุทธ์จากบนลงล่าง กลยุทธ์ใดดีที่สุดขึ้นอยู่กับลักษณะของกราฟการขึ้นต่อกัน

  • ระหว่างการยกเลิกการใช้งานจากล่างขึ้นบน หลังจากสร้างกราฟและทราบชุดอินพุตที่เปลี่ยนแปลงแล้ว ระบบจะยกเลิกการใช้งานโหนดทั้งหมดที่ขึ้นต่อกันแบบส่งต่อกับไฟล์ที่เปลี่ยนแปลง กลยุทธ์นี้ดีที่สุดหากมีการสร้างโหนดระดับบนสุดเดียวกันอีกครั้ง โปรดทราบว่าการยกเลิกการใช้งานจากล่างขึ้นบนต้องใช้ stat() กับไฟล์อินพุตทั้งหมดของการบิลด์ก่อนหน้าเพื่อตรวจสอบว่ามีการเปลี่ยนแปลงหรือไม่ คุณสามารถปรับปรุงประสิทธิภาพได้โดยใช้ inotify หรือกลไกที่คล้ายกันเพื่อเรียนรู้เกี่ยวกับไฟล์ที่เปลี่ยนแปลง

  • ระหว่างการยกเลิกการใช้งานจากบนลงล่าง ระบบจะตรวจสอบการปิดการส่งต่อของโหนดระดับบนสุด และเก็บเฉพาะโหนดที่มีการปิดการส่งต่อที่สะอาด กลยุทธ์นี้ดีกว่าหากกราฟโหนดมีขนาดใหญ่ แต่การบิลด์ครั้งถัดไปต้องใช้เพียงส่วนย่อยๆ ของกราฟเท่านั้น การยกเลิกการใช้งานจากล่างขึ้นบนจะยกเลิกการใช้งานกราฟที่ใหญ่กว่าของการบิลด์ครั้งแรก ซึ่งแตกต่างจากการยกเลิกการใช้งานจากบนลงล่างที่จะเพียงแค่เรียกใช้กราฟขนาดเล็กของการบิลด์ครั้งที่ 2

Bazel จะยกเลิกการใช้งานจากล่างขึ้นบนเท่านั้น

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

กลยุทธ์นี้มีประโยชน์ เช่น หากคุณเปลี่ยนความคิดเห็นในไฟล์ C++ ไฟล์ .o ที่สร้างจากไฟล์นั้นจะเหมือนเดิม จึงไม่จำเป็นต้องเรียกใช้ Linker อีกครั้ง

การลิงก์ / การคอมไพล์แบบเพิ่ม

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

  • การลิงก์แบบเพิ่ม
  • เมื่อไฟล์คลาสเดียวมีการเปลี่ยนแปลงในไฟล์ JAR คุณสามารถแก้ไขไฟล์ JAR ในตำแหน่งเดิมแทนที่จะสร้างใหม่ตั้งแต่ต้น

เหตุผลที่ Bazel ไม่รองรับสิ่งเหล่านี้อย่างเป็นหลักการมี 2 ประการ ได้แก่

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

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

การแมปกับแนวคิดของ Bazel

นี่คือข้อมูลสรุประดับสูงของการใช้งาน SkyFunction และ SkyValue หลักที่ Bazel ใช้ในการบิลด์

  • FileStateValue ผลลัพธ์ของ lstat() สำหรับไฟล์ที่มีอยู่ ฟังก์ชันจะคำนวณข้อมูลเพิ่มเติมเพื่อตรวจหาการเปลี่ยนแปลงในไฟล์ด้วย นี่คือโหนดระดับต่ำสุดในกราฟ Skyframe และไม่มีการขึ้นต่อกัน
  • FileValue ใช้โดยทุกสิ่งทุกอย่างที่สนใจเนื้อหาจริงหรือเส้นทางที่แก้ไขแล้วของไฟล์ ขึ้นอยู่กับ FileStateValue ที่เกี่ยวข้องและ ลิงก์สัญลักษณ์ที่ต้องแก้ไข (เช่น FileValue สำหรับ a/b ต้องใช้เส้นทางที่แก้ไขแล้วของ a และเส้นทางที่แก้ไขแล้วของ a/b) ความ แตกต่างระหว่าง FileValue กับ FileStateValue มีความสำคัญเนื่องจาก FileStateValue สามารถใช้ในกรณีที่ไม่จำเป็นต้องใช้เนื้อหาของไฟล์จริง ตัวอย่างเช่น เนื้อหาของไฟล์ไม่เกี่ยวข้องเมื่อ ประเมิน Globs ของระบบไฟล์ (เช่น srcs=glob(["*/*.java"]))
  • DirectoryListingStateValue ผลลัพธ์ของ readdir() เช่นเดียวกับ FileStateValue นี่คือโหนดระดับต่ำสุดและไม่มีการขึ้นต่อกัน
  • DirectoryListingValue ใช้โดยทุกสิ่งทุกอย่างที่สนใจรายการของไดเรกทอรี ขึ้นอยู่กับ DirectoryListingStateValue ที่เกี่ยวข้อง รวมถึง FileValue ที่เชื่อมโยงของไดเรกทอรี
  • PackageValue แสดงเวอร์ชันที่แยกวิเคราะห์แล้วของไฟล์ BUILD ขึ้นอยู่กับ FileValue ของไฟล์ BUILD ที่เชื่อมโยง รวมถึงขึ้นต่อกันแบบส่งต่อกับ DirectoryListingValue ที่ใช้ในการแก้ไข Globs ในแพ็กเกจ (โครงสร้างข้อมูลที่แสดงเนื้อหาของไฟล์ BUILD ภายใน)
  • ConfiguredTargetValue แสดงเป้าหมายที่กำหนดค่า ซึ่งเป็นทูเพิลของชุดการดำเนินการที่สร้างขึ้นระหว่างการวิเคราะห์เป้าหมายและข้อมูลที่ให้ไว้กับเป้าหมายที่กำหนดค่าที่ขึ้นต่อกัน ขึ้นอยู่กับ PackageValue ที่เป้าหมายที่เกี่ยวข้องอยู่ ConfiguredTargetValues ของการขึ้นต่อกันโดยตรง และโหนดพิเศษที่แสดงการบิลด์ การกำหนดค่า
  • ArtifactValue แสดงไฟล์ในการบิลด์ ไม่ว่าจะเป็นอาร์ติแฟกต์ต้นฉบับหรืออาร์ติแฟกต์เอาต์พุต อาร์ติแฟกต์เกือบจะเทียบเท่ากับไฟล์ และใช้เพื่ออ้างอิงไฟล์ระหว่างการดำเนินการจริงของขั้นตอนการบิลด์ ไฟล์ต้นฉบับขึ้นอยู่กับ FileValue ของโหนดที่เชื่อมโยง และอาร์ติแฟกต์เอาต์พุตขึ้นอยู่กับ ActionExecutionValue ของการดำเนินการใดก็ตามที่สร้างอาร์ติแฟกต์
  • ActionExecutionValue แสดงการดำเนินการของการดำเนินการ ขึ้นอยู่กับ ArtifactValues ของไฟล์อินพุต การดำเนินการที่ดำเนินการจะอยู่ใน SkyKey ซึ่งขัดแย้งกับแนวคิดที่ว่า SkyKey ควรมีขนาดเล็ก โปรดทราบว่าระบบจะไม่ใช้ ActionExecutionValue และ ArtifactValue หากไม่ได้เรียกใช้ระยะการดำเนินการ

แผนภาพนี้แสดงความสัมพันธ์ระหว่างการใช้งาน SkyFunction หลังจากบิลด์ของ Bazel เอง เพื่อช่วยให้เห็นภาพ

กราฟความสัมพันธ์ของการติดตั้งใช้งาน SkyFunction