สกายเฟรม

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

การประเมินแบบขนานและรูปแบบการเพิ่มขึ้นของ Bazel

โมเดลข้อมูล

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

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

การประเมิน

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

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

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

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

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

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

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

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

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

ส่วนเพิ่ม

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

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

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

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

Bazel จะทำการลบล้างจากด้านล่างขึ้นเท่านั้น

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

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

การลิงก์ / การคอมไพล์ที่เพิ่มขึ้น

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

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

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

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

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

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

ต่อไปนี้เป็นภาพรวมระดับสูงของการใช้งาน SkyFunction และ SkyValue ที่สำคัญซึ่ง Bazel ใช้เพื่อทำการบิลด์

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

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

กราฟความสัมพันธ์ในการใช้งาน SkyFunction