การประเมินแบบขนานและรูปแบบการเพิ่มขึ้นของ Bazel
โมเดลข้อมูล
โมเดลข้อมูลประกอบด้วยรายการต่อไปนี้
SkyValue
หรือที่เรียกว่าโหนดSkyValues
คือออบเจ็กต์แบบคงที่ซึ่งมีข้อมูลทั้งหมดที่สร้างในระหว่างการสร้างและอินพุตของการสร้าง เช่น ไฟล์อินพุต ไฟล์เอาต์พุต เป้าหมาย และเป้าหมายที่กําหนดค่าไว้SkyKey
. ชื่อสั้นที่แก้ไขไม่ได้เพื่ออ้างอิงSkyValue
เช่นFILECONTENTS:/tmp/foo
หรือPACKAGE://foo
SkyFunction
. สร้างโหนดตามคีย์และโหนดที่เกี่ยวข้อง- กราฟโหนด โครงสร้างข้อมูลที่แสดงความสัมพันธ์แบบพึ่งพาระหว่างโหนด
Skyframe
. ชื่อโค้ดของเฟรมเวิร์กการประเมินแบบเพิ่มที่ Bazel สร้างขึ้น
การประเมิน
บิลด์ประกอบด้วยการประเมินโหนดที่แสดงคําขอบิลด์ (นี่คือสถานะที่เราพยายามทำให้ได้ แต่มีโค้ดเดิมจำนวนมากที่ขัดขวาง) ก่อนอื่น ระบบจะค้นหาและเรียกใช้ SkyFunction
โดยใช้คีย์ของ SkyKey
ระดับบนสุด จากนั้นฟังก์ชันจะขอการประเมินโหนดที่จําเป็นต่อการประเมินโหนดระดับบนสุด ซึ่งจะส่งผลให้มีการเรียกใช้ฟังก์ชันอื่นๆ และอื่นๆ ต่อไปจนกว่าจะถึงโหนดใบ (ซึ่งมักจะเป็นโหนดที่แสดงไฟล์อินพุตในระบบไฟล์) สุดท้าย เราจะได้รับค่าของ 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 เท่านั้น
ปัจจุบันเราทำการลบล้างจากล่างขึ้นบนเท่านั้น
เราใช้การตัดการเปลี่ยนแปลงเพื่อให้ได้การเพิ่มประสิทธิภาพเพิ่มเติม หากโหนดหนึ่งๆ ใช้งานไม่ได้ แต่พบว่าค่าใหม่ของโหนดนั้นเหมือนกับค่าเดิมเมื่อสร้างใหม่ โหนดที่ใช้งานไม่ได้เนื่องจากการเปลี่ยนแปลงในโหนดนี้จะ "ฟื้นคืนชีพ"
ซึ่งจะมีประโยชน์ เช่น หากมีการเปลี่ยนแปลงความคิดเห็นในไฟล์ C++ ไฟล์ .o
ที่สร้างขึ้นจากไฟล์ดังกล่าวก็จะเหมือนกัน ดังนั้นเราจึงไม่ต้องเรียกใช้ linker อีกครั้ง
การลิงก์ / การคอมไพล์ที่เพิ่มขึ้น
ข้อจํากัดหลักของรูปแบบนี้คือ สถานะไม่ถูกต้องของโหนดจะเป็นแบบทั้งหมดหรือไม่มีเลย เมื่อมีการทําการเปลี่ยนแปลงทรัพยากร โหนดที่ขึ้นต่อกันจะสร้างขึ้นใหม่ตั้งแต่ต้นเสมอ แม้ว่าจะมีอัลกอริทึมที่ดีกว่าซึ่งจะเปลี่ยนค่าเดิมของโหนดตามการเปลี่ยนแปลงก็ตาม ตัวอย่างที่การดําเนินการนี้จะมีประโยชน์
- การลิงก์ที่เพิ่มขึ้น
- เมื่อไฟล์
.class
ไฟล์เดียวมีการเปลี่ยนแปลงใน.jar
ในทางทฤษฎีแล้ว เราอาจแก้ไขไฟล์.jar
แทนการสร้างใหม่ตั้งแต่ต้นได้
สาเหตุที่ปัจจุบัน Bazel ไม่รองรับสิ่งเหล่านี้อย่างมีหลักการ (เรารองรับการลิงก์แบบเพิ่มบางส่วน แต่ไม่ได้ติดตั้งใช้งานภายใน Skyframe) มี 2 ประการคือ เราได้รับประสิทธิภาพเพิ่มขึ้นเพียงเล็กน้อยและรับประกันได้ยากว่าผลลัพธ์ของการเปลี่ยนแปลงจะเหมือนกับการรีบิลด์ใหม่ทั้งหมด และ Google ให้ความสำคัญกับบิลด์ที่ซ้ำกันแบบทุกๆ บิต
ที่ผ่านมา เรามักจะได้ประสิทธิภาพที่ดีพอเสมอด้วยวิธีแยกขั้นตอนการสร้างที่ใช้เวลานานออกและประเมินอีกครั้งบางส่วนด้วยวิธีนี้ ซึ่งก็คือการแยกคลาสทั้งหมดในแอปออกเป็นหลายกลุ่มและทำการแยกเป็นไฟล์แยกกัน วิธีนี้ช่วยให้คุณไม่ต้องจัดทําการจัดทําดัชนีอีกครั้งหากชั้นเรียนในกลุ่มไม่มีการเปลี่ยนแปลง
การแมปกับแนวคิดของ Bazel
ภาพรวมคร่าวๆ ของSkyFunction
การใช้งานบางส่วนที่ Bazel ใช้เพื่อทำการบิลด์มีดังนี้
- FileStateValue ผลลัพธ์ของ
lstat()
สำหรับไฟล์ที่มีอยู่ เราจะคำนวณข้อมูลเพิ่มเติมเพื่อตรวจหาการเปลี่ยนแปลงในไฟล์ด้วย ซึ่งเป็นโหนดระดับต่ำสุดในกราฟ Skyframe และไม่มีทรัพยากรที่ต้องพึ่งพา - FileValue ใช้โดยสิ่งที่สนใจเนื้อหาจริงและ/หรือเส้นทางที่แก้ไขแล้วของไฟล์ ขึ้นอยู่กับ
FileStateValue
ที่เกี่ยวข้องและลิงก์สัญลักษณ์ที่ต้องแก้ไข (เช่นFileValue
สำหรับa/b
ต้องใช้เส้นทางที่แก้ไขแล้วของa
และเส้นทางที่แก้ไขแล้วของa/b
) การแยกความแตกต่างระหว่างFileStateValue
นั้นสำคัญเนื่องจากในบางกรณี (เช่น การประเมินนิพจน์ทั่วไปของระบบไฟล์ (เช่นsrcs=glob(["*/*.java"])
) ไม่จำเป็นต้องใช้เนื้อหาของไฟล์ - DirectoryListingValue โดยทั่วไปแล้ว ผลลัพธ์ของ
readdir()
ขึ้นอยู่กับFileValue
ที่เชื่อมโยงกับไดเรกทอรี - PackageValue แสดงเวอร์ชันที่แยกวิเคราะห์ของไฟล์ BUILD ขึ้นอยู่กับ
FileValue
ของไฟล์BUILD
ที่เชื่อมโยง และขึ้นอยู่กับDirectoryListingValue
ที่ใช้เพื่อแก้ไขนิพจน์ทั่วไปในแพ็กเกจ (โครงสร้างข้อมูลที่แสดงถึงเนื้อหาของไฟล์BUILD
ภายใน) ด้วย - ConfiguredTargetValue แสดงเป้าหมายที่กําหนดค่าไว้ ซึ่งเป็นทูเปิลของชุดการดําเนินการที่สร้างขึ้นระหว่างการวิเคราะห์เป้าหมายและข้อมูลที่ให้ไว้กับเป้าหมายที่กําหนดค่าไว้ซึ่งขึ้นอยู่กับเป้าหมายนี้ ขึ้นอยู่กับ
PackageValue
ของเป้าหมายที่เกี่ยวข้องConfiguredTargetValues
ของข้อกําหนดโดยตรง และโหนดพิเศษที่แสดงการกําหนดค่าบิลด์ - ArtifactValue แสดงไฟล์ในบิลด์ ไม่ว่าจะเป็นแหล่งที่มาหรืออาร์ติแฟกต์เอาต์พุต (อาร์ติแฟกต์เกือบจะเทียบเท่ากับไฟล์ และใช้สําหรับอ้างอิงไฟล์ในระหว่างการดําเนินการจริงของขั้นตอนการสร้าง) สำหรับไฟล์ต้นฉบับ
FileValue
จะขึ้นอยู่กับโหนดที่เชื่อมโยง ส่วนสำหรับอาร์ติแฟกต์เอาต์พุตActionExecutionValue
จะขึ้นอยู่กับการดำเนินการใดก็ตามที่สร้างอาร์ติแฟกต์ - ActionExecutionValue แสดงการดำเนินการ ขึ้นอยู่กับ
ArtifactValues
ของไฟล์อินพุต การดำเนินการที่ดำเนินการอยู่ในปัจจุบันอยู่ใน Sky Key ซึ่งขัดต่อแนวคิดที่ว่า Sky Key ควรมีขนาดเล็ก เรากําลังหาวิธีแก้ไขความคลาดเคลื่อนนี้ (โปรดทราบว่าActionExecutionValue
และArtifactValue
จะไม่ได้ใช้หากเราไม่เรียกใช้ระยะการดําเนินการใน Skyframe)