การประเมินแบบขนานและรูปแบบการเพิ่มขึ้นของ 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 เองเพื่อเป็นภาพประกอบ