โมเดลการประเมินแบบขนานและการทำงานแบบเพิ่มของ Bazel
โมเดลข้อมูล
โมเดลข้อมูลประกอบด้วยรายการต่อไปนี้
SkyValueหรือที่เรียกว่าโหนดSkyValuesเป็นออบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ซึ่ง มีข้อมูลทั้งหมดที่สร้างขึ้นในระหว่างการสร้างและอินพุตของ การสร้าง ตัวอย่างเช่น ไฟล์อินพุต ไฟล์เอาต์พุต เป้าหมาย และเป้าหมายที่กำหนดค่าไว้SkyKeyชื่อย่อที่เปลี่ยนแปลงไม่ได้เพื่อใช้อ้างอิงSkyValueเช่นFILECONTENTS:/tmp/fooหรือPACKAGE://fooSkyFunctionสร้างโหนดตามคีย์และโหนดที่ขึ้นต่อกัน- กราฟโหนด โครงสร้างข้อมูลที่มีความสัมพันธ์แบบขึ้นต่อกันระหว่างโหนด
Skyframe. ชื่อรหัสสำหรับเฟรมเวิร์กการประเมินแบบเพิ่มที่ Bazel ใช้
การประเมิน
การสร้างจะทำได้โดยการประเมินโหนดที่แสดงคำขอสร้าง
ก่อนอื่น Bazel จะค้นหา SkyFunction ที่สอดคล้องกับคีย์ของระดับบนสุด
SkyKey จากนั้นฟังก์ชันจะขอให้ประเมินโหนดที่ต้องใช้เพื่อประเมินโหนดระดับบนสุด ซึ่งจะส่งผลให้เกิดการเรียก SkyFunction อื่นๆ
จนกว่าจะถึงโหนดใบ โดยปกติแล้ว Leaf Node จะเป็นโหนดที่แสดง
ไฟล์อินพุตในระบบไฟล์ สุดท้าย Bazel จะมีค่าของ SkyValue ระดับบนสุด ผลข้างเคียงบางอย่าง (เช่น ไฟล์เอาต์พุตในระบบไฟล์) และกราฟแบบมีทิศทางแบบไม่มีวงจรของการขึ้นต่อกันระหว่างโหนดที่เกี่ยวข้องในการสร้าง
SkyFunctionสามารถขอ SkyKeys ได้หลายครั้งหากไม่สามารถระบุล่วงหน้าถึงโหนดทั้งหมดที่ต้องใช้ในการทำงาน ตัวอย่างง่ายๆ คือการประเมิน
โหนดไฟล์อินพุตที่กลายเป็น Symlink ฟังก์ชันจะพยายามอ่าน
ไฟล์ ตระหนักว่าไฟล์นั้นเป็น Symlink จึงดึงโหนดระบบไฟล์
ที่แสดงถึงเป้าหมายของ Symlink แต่ตัวฟังก์ชันเองก็อาจเป็น Symlink ได้เช่นกัน ในกรณีนี้ฟังก์ชันเดิมจะต้องดึงข้อมูลเป้าหมายด้วย
ฟังก์ชันจะแสดงในโค้ดโดยอินเทอร์เฟซ SkyFunction และ
บริการที่อินเทอร์เฟซชื่อ SkyFunction.Environment จัดให้ ฟังก์ชันทำสิ่งต่อไปนี้ได้
- ขอรับการประเมินโหนดอื่นโดยการเรียกใช้
env.getValueหาก โหนดพร้อมใช้งาน ระบบจะแสดงค่าของโหนดนั้น ไม่เช่นนั้น ระบบจะแสดงnullและคาดว่าฟังก์ชันจะแสดงnullในกรณีหลัง ระบบจะประเมินโหนดที่ขึ้นต่อกัน แล้วเรียกใช้ตัวสร้างโหนดเดิมอีกครั้ง แต่ครั้งนี้การเรียกenv.getValueเดียวกันจะแสดงผลค่าที่ไม่ใช่null - ขอรับการประเมินโหนดอื่นๆ หลายรายการโดยการเรียกใช้
env.getValues()ซึ่งจะทำงานเหมือนกันทุกประการ ยกเว้นว่าโหนดที่ขึ้นต่อกันจะ ได้รับการประเมินแบบขนาน - ทำการคำนวณระหว่างการเรียกใช้
- มีผลข้างเคียง เช่น การเขียนไฟล์ลงในระบบไฟล์ ต้องระมัดระวังไม่ให้ฟังก์ชันที่แตกต่างกัน 2 ฟังก์ชันรบกวนการทำงานของกันและกัน โดยทั่วไป ผลข้างเคียงในการเขียน (ที่ข้อมูลไหลออกจาก Bazel) ใช้ได้ แต่ผลข้างเคียงในการอ่าน (ที่ข้อมูลไหลเข้าสู่ Bazel โดยไม่มี การขึ้นทะเบียนการอ้างอิง) ใช้ไม่ได้ เนื่องจากเป็นการอ้างอิงที่ไม่ได้ลงทะเบียน และอาจทำให้การสร้างแบบเพิ่มทีละรายการไม่ถูกต้อง
การใช้งาน SkyFunction ที่ดีจะหลีกเลี่ยงการเข้าถึงข้อมูลด้วยวิธีอื่น
นอกเหนือจากการขอทรัพยากร Dependency (เช่น การอ่านระบบไฟล์โดยตรง)
เนื่องจากจะทำให้ Bazel ไม่ได้ลงทะเบียนทรัพยากร Dependency ของข้อมูลในไฟล์
ที่อ่าน จึงทำให้การสร้างแบบเพิ่มไม่ถูกต้อง
เมื่อฟังก์ชันมีข้อมูลเพียงพอที่จะทำงานแล้ว ฟังก์ชันควรแสดงผลค่าที่ไม่ใช่ null
เพื่อระบุว่าเสร็จสมบูรณ์แล้ว
กลยุทธ์การประเมินนี้มีประโยชน์หลายประการ ดังนี้
- การเป็นระบบปิด หากฟังก์ชันขอข้อมูลอินพุตโดยขึ้นอยู่กับโหนดอื่นๆ เท่านั้น Bazel จะรับประกันได้ว่าหากสถานะอินพุตเหมือนกัน ระบบจะแสดงผลข้อมูลเดียวกัน หากฟังก์ชันท้องฟ้าทั้งหมดเป็นแบบดีเทอร์มินิสติก แสดงว่าบิลด์ทั้งหมดจะเป็นแบบดีเทอร์มินิสติกด้วย
- วัดการเพิ่มขึ้นที่ถูกต้องและสมบูรณ์แบบ หากบันทึกข้อมูลอินพุตทั้งหมดของฟังก์ชันทั้งหมด Bazel จะล้างข้อมูลเฉพาะชุดโหนดที่ต้อง ล้างข้อมูลเมื่อข้อมูลอินพุตเปลี่ยนแปลงได้
- การประมวลผลแบบคู่ขนาน เนื่องจากฟังก์ชันจะโต้ตอบกันได้โดยการขอทรัพยากร Dependency เท่านั้น ฟังก์ชันที่ไม่ได้ขึ้นต่อกันจึงสามารถ เรียกใช้แบบขนานได้ และ Bazel รับประกันได้ว่าผลลัพธ์จะเหมือนกับการเรียกใช้แบบตามลำดับ
ส่วนเพิ่ม
เนื่องจากฟังก์ชันจะเข้าถึงข้อมูลอินพุตได้โดยขึ้นอยู่กับโหนดอื่นๆ เท่านั้น Bazel จึงสร้างกราฟโฟลว์ข้อมูลที่สมบูรณ์จากไฟล์อินพุตไปยังไฟล์เอาต์พุตได้ และใช้ข้อมูลนี้เพื่อสร้างโหนดที่ต้องสร้างใหม่เท่านั้น ซึ่งก็คือการปิดทรานซิทีฟแบบย้อนกลับของชุดไฟล์อินพุตที่เปลี่ยนแปลง
โดยเฉพาะอย่างยิ่ง กลยุทธ์การเพิ่มขึ้นที่เป็นไปได้ 2 อย่าง ได้แก่ กลยุทธ์จากล่างขึ้นบน และกลยุทธ์จากบนลงล่าง ซึ่งจะขึ้นอยู่กับลักษณะของกราฟทรัพยากร Dependency
ในระหว่างการลบล้างจากล่างขึ้นบน หลังจากสร้างกราฟและทราบชุดอินพุตที่เปลี่ยนแปลงแล้ว ระบบจะลบล้างโหนดทั้งหมดที่ขึ้นอยู่กับไฟล์ที่เปลี่ยนแปลงโดยอ้อม ซึ่งเหมาะที่สุดหากจะสร้างโหนดระดับบนสุดเดียวกันอีกครั้ง โปรดทราบว่าการลบล้างจากล่างขึ้นบนต้องใช้
stat()ในไฟล์อินพุตทั้งหมดของบิลด์ก่อนหน้าเพื่อพิจารณาว่ามีการเปลี่ยนแปลงหรือไม่ ซึ่งสามารถปรับปรุงได้โดยใช้inotifyหรือกลไกที่คล้ายกันเพื่อเรียนรู้เกี่ยวกับ ไฟล์ที่มีการเปลี่ยนแปลงในระหว่างการลบล้างจากบนลงล่าง ระบบจะตรวจสอบการปิดทรานซิทีฟของโหนดระดับบนสุด และจะเก็บเฉพาะโหนดที่มีการปิดทรานซิทีฟที่สะอาดไว้ วิธีนี้จะดีกว่าหากกราฟโหนดมีขนาดใหญ่ แต่การสร้างครั้งถัดไปต้องการเพียง ส่วนย่อยๆ ของกราฟนั้น การลบล้างจากล่างขึ้นบนจะลบล้างกราฟที่ใหญ่กว่า ของการสร้างครั้งแรก ซึ่งต่างจากการลบล้างจากบนลงล่างที่จะเพียงแค่เดินผ่านกราฟขนาดเล็ก ของการสร้างครั้งที่ 2
Bazel จะทำการล้างข้อมูลจากล่างขึ้นบนเท่านั้น
Bazel ใช้การตัดแต่งการเปลี่ยนแปลงเพื่อให้ได้การเพิ่มขึ้นเพิ่มเติม กล่าวคือ หากโหนดไม่ถูกต้อง แต่เมื่อสร้างใหม่ พบว่าค่าใหม่เหมือนกับค่าเก่า โหนดที่ถูกทำให้ไม่ถูกต้องเนื่องจากการเปลี่ยนแปลงในโหนดนี้จะ "ฟื้นคืน"
ซึ่งจะมีประโยชน์ เช่น หากมีการเปลี่ยนแปลงความคิดเห็นในไฟล์ C++
.o ไฟล์ที่สร้างจากไฟล์ดังกล่าวจะเหมือนกัน จึงไม่จำเป็นต้องเรียกใช้
ตัวลิงก์อีกครั้ง
การลิงก์ / การคอมไพล์ที่เพิ่มขึ้น
ข้อจำกัดหลักของโมเดลนี้คือการลบล้างโหนดเป็นแบบ ทั้งหมดหรือไม่มีเลย เมื่อมีการเปลี่ยนแปลงการอ้างอิง ระบบจะสร้างโหนดที่ขึ้นต่อกันใหม่ตั้งแต่ต้นเสมอ แม้ว่าจะมีอัลกอริทึมที่ดีกว่าซึ่งจะเปลี่ยนค่าเก่าของโหนดตามการเปลี่ยนแปลงก็ตาม ตัวอย่างบางส่วนที่การดำเนินการนี้จะมีประโยชน์ ได้แก่
- การลิงก์แบบเพิ่ม
- เมื่อไฟล์คลาสเดียวมีการเปลี่ยนแปลงในไฟล์ JAR คุณจะแก้ไขไฟล์ JAR ในตำแหน่งเดิมได้แทนที่จะสร้างใหม่ตั้งแต่ต้น
เหตุผลที่ Bazel ไม่รองรับสิ่งเหล่านี้อย่างเป็นหลักการ มี 2 ประการ
- ประสิทธิภาพเพิ่มขึ้นเพียงเล็กน้อย
- ความยากในการตรวจสอบว่าผลลัพธ์ของการเปลี่ยนแปลงนั้นเหมือนกับการสร้างใหม่ที่สะอาด และ Google ให้ความสำคัญกับการสร้างที่ทำซ้ำได้แบบบิตต่อบิต
จนถึงตอนนี้ คุณสามารถบรรลุประสิทธิภาพที่ดีพอได้โดยการแยกขั้นตอนการสร้างที่มีค่าใช้จ่ายสูงและทำการประเมินบางส่วนด้วยวิธีดังกล่าว เช่น ในแอป Android คุณสามารถแยกคลาสทั้งหมดออกเป็นหลายกลุ่มและ dex แต่ละกลุ่มแยกกันได้ ด้วยวิธีนี้ หากชั้นเรียนในกลุ่มไม่มีการเปลี่ยนแปลง คุณก็ไม่ต้องทำ dexing ใหม่
การแมปกับแนวคิดของ Bazel
ต่อไปนี้คือสรุปภาพรวมของการใช้งานคีย์ SkyFunction และ SkyValue
ที่ Bazel ใช้เพื่อทำการบิลด์
- FileStateValue ผลลัพธ์ของ
lstat()สำหรับไฟล์ที่มีอยู่ ฟังก์ชัน จะคำนวณข้อมูลเพิ่มเติมเพื่อตรวจหาการเปลี่ยนแปลงใน ไฟล์ด้วย นี่คือโหนดระดับต่ำสุดในกราฟ Skyframe และไม่มี การอ้างอิง - FileValue ใช้โดยทุกอย่างที่สนใจเนื้อหาจริงหรือเส้นทางที่แก้ไขแล้วของไฟล์ ขึ้นอยู่กับ
FileStateValueที่เกี่ยวข้องและ ซิมลิงก์ที่ต้องแก้ไข (เช่นFileValueสำหรับa/bต้องมีเส้นทางที่แก้ไขแล้วของaและเส้นทางที่แก้ไขแล้วของa/b) ความแตกต่างระหว่างFileValueกับFileStateValueเป็นสิ่งสำคัญเนื่องจาก สามารถใช้FileStateValueในกรณีที่ไม่จำเป็นต้องใช้เนื้อหาของไฟล์ จริงๆ ตัวอย่างเช่น เนื้อหาของไฟล์ไม่เกี่ยวข้องเมื่อประเมิน Glob ของระบบไฟล์ (เช่นsrcs=glob(["*/*.java"])) - DirectoryListingStateValue ผลลัพธ์ของ
readdir()เช่นFileStateValueนี่คือโหนดระดับล่างสุดและไม่มีการขึ้นต่อกัน - DirectoryListingValue ใช้โดยทุกอย่างที่สนใจรายการของไดเรกทอรี ขึ้นอยู่กับ
DirectoryListingStateValueที่เกี่ยวข้อง รวมถึงFileValueที่เชื่อมโยงของไดเรกทอรี - PackageValue แสดงเวอร์ชันที่แยกวิเคราะห์แล้วของไฟล์ BUILD ขึ้นอยู่กับ
FileValueของไฟล์BUILDที่เชื่อมโยง และยังขึ้นอยู่กับDirectoryListingValueที่ใช้เพื่อแก้ไข Glob ในแพ็กเกจ (โครงสร้างข้อมูลที่แสดงเนื้อหาของไฟล์BUILDภายใน) - ConfiguredTargetValue แสดงถึงเป้าหมายที่กำหนดค่า ซึ่งเป็น Tuple
ของชุดการดำเนินการที่สร้างขึ้นระหว่างการวิเคราะห์เป้าหมายและ
ข้อมูลที่ให้ไว้กับเป้าหมายที่กำหนดค่าไว้ซึ่งขึ้นอยู่กับเป้าหมายนั้น ขึ้นอยู่กับ
PackageValueเป้าหมายที่เกี่ยวข้องอยู่ConfiguredTargetValuesของการอ้างอิงโดยตรง และโหนดพิเศษที่แสดงถึงการกำหนดค่า บิลด์ - ArtifactValue แสดงไฟล์ในการสร้าง ไม่ว่าจะเป็นแหล่งที่มาหรืออาร์ติแฟกต์เอาต์พุต อาร์ติแฟกต์เกือบจะเทียบเท่ากับไฟล์ และใช้เพื่อ
อ้างอิงถึงไฟล์ในระหว่างการดำเนินการจริงของขั้นตอนการสร้าง ไฟล์ต้นฉบับ
ขึ้นอยู่กับ
FileValueของโหนดที่เชื่อมโยง และอาร์ติแฟกต์เอาต์พุต ขึ้นอยู่กับActionExecutionValueของการดำเนินการใดก็ตามที่สร้าง อาร์ติแฟกต์ - ActionExecutionValue แสดงการดำเนินการ ขึ้นอยู่กับ
ArtifactValuesของไฟล์อินพุต การดำเนินการที่ดำเนินการจะอยู่ใน SkyKey ซึ่งขัดแย้งกับแนวคิดที่ว่า SkyKey ควรมีขนาดเล็ก โปรดทราบว่าActionExecutionValueและArtifactValueจะไม่ได้ใช้หาก เฟสการดำเนินการไม่ทำงาน
แผนภาพนี้แสดงความสัมพันธ์ระหว่างการติดตั้งใช้งาน SkyFunction หลังจากบิลด์ของ Bazel เองเพื่อเป็นเครื่องมือช่วยในการมองเห็น
