สกายเฟรม

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

โมเดลข้อมูล

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

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

การประเมิน

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

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 จะยกเลิกได้เฉพาะชุดโหนดที่ถูกต้องซึ่งจำเป็นต้องทำให้ใช้งานไม่ได้เมื่อข้อมูลอินพุตมีการเปลี่ยนแปลง
  • การทำงานพร้อมกัน เนื่องจากฟังก์ชันต่างๆ สามารถโต้ตอบซึ่งกันและกันด้วยการขอทรัพยากร Dependency เท่านั้น ฟังก์ชันที่ไม่ได้อ้างอิงกันและกันจึงจะเรียกใช้พร้อมกันได้ และ Bazel รับประกันได้ว่าผลลัพธ์จะเหมือนกับการเรียกใช้ตามลำดับ

ส่วนเพิ่ม

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

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

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

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

ขณะนี้เราดำเนินการโมฆะจากล่างขึ้นบนเท่านั้น

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

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

การลิงก์ที่เพิ่มขึ้น / การรวบรวม

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

  • การลิงก์ที่เพิ่มขึ้น
  • เมื่อไฟล์ .class ไฟล์เดียวมีการเปลี่ยนแปลงใน .jar ในทางทฤษฎีแล้ว เราจะแก้ไขไฟล์ .jar แทนที่จะสร้างไฟล์ใหม่ตั้งแต่ต้นอีกครั้ง

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

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

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

ต่อไปนี้เป็นภาพรวมคร่าวๆ ของการติดตั้งใช้งาน SkyFunction บางส่วนที่ Bazel ใช้ในการสร้างบิลด์

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