การประเมินพร้อมกันและโมเดลส่วนเพิ่มของ 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)