การประเมินแบบคู่ขนานและโมเดลส่วนเพิ่มของ Bazel
โมเดลข้อมูล
โมเดลข้อมูลประกอบด้วยรายการต่อไปนี้
SkyValue
เรียกอีกอย่างว่าโหนดSkyValues
เป็นวัตถุที่เปลี่ยนแปลงไม่ได้ มีข้อมูลทั้งหมดที่สร้างขึ้นในช่วงบิลด์ และอินพุตของ งานสร้าง ตัวอย่างเช่น ไฟล์อินพุต ไฟล์เอาต์พุต เป้าหมาย และที่กำหนดค่าไว้ เป้าหมายSkyKey
ชื่อสั้นๆ ที่เปลี่ยนแปลงไม่ได้เพื่ออ้างอิงถึงSkyValue
เช่นFILECONTENTS:/tmp/foo
หรือPACKAGE://foo
SkyFunction
สร้างโหนดโดยอิงตามคีย์และโหนดที่อ้างอิง- กราฟโหนด โครงสร้างข้อมูลที่มีความสัมพันธ์แบบการขึ้นต่อกันระหว่าง
Skyframe
ชื่อโค้ดของเฟรมเวิร์กการประเมินส่วนเพิ่ม Bazel คือ อิงจาก
การประเมิน
บิลด์ประกอบด้วยการประเมินโหนดที่แสดงถึงคำขอบิลด์ (นี่คือสถานะที่เราต้องการ แต่มีโค้ดเดิมจำนวนมากอยู่ระหว่างดำเนินการ) ก่อนอื่น ระบบพบ SkyFunction
และเรียกใช้ด้วยคีย์ของ SkyKey
ระดับบนสุด จากนั้นฟังก์ชันจะขอการประเมินโหนดที่ต้องใช้ในการประเมินโหนดระดับบนสุด ซึ่งส่งผลให้เกิดการเรียกใช้ฟังก์ชันอื่นๆ เป็นเช่นนี้ไปเรื่อยๆ จนกระทั่งถึงโหนด Leaf (ซึ่งมักเป็นโหนดที่แสดงถึงไฟล์อินพุตในระบบไฟล์) สุดท้าย เราจะจบด้วยค่าของ SkyValue
ระดับบนสุด ผลข้างเคียงบางอย่าง (เช่น ไฟล์เอาต์พุตในระบบไฟล์) และกราฟแบบวนซ้ำที่มีทิศทางของทรัพยากร Dependency ระหว่างโหนดที่เกี่ยวข้องในการสร้าง
SkyFunction
สามารถขอ SkyKeys
ในหลายบัตรได้หากบอกโหนดทั้งหมดที่จำเป็นต้องใช้ในการทำงานล่วงหน้าไม่ได้ ตัวอย่างง่ายๆ คือการประเมินโหนดไฟล์อินพุตที่กลายเป็นลิงก์สัญลักษณ์ โดยฟังก์ชันจะพยายามอ่านไฟล์แล้วรู้ว่าเป็นลิงก์สัญลักษณ์ จึงดึงโหนดระบบไฟล์ที่เป็นตัวแทนเป้าหมายของลิงก์สัญลักษณ์ แต่ผลลัพธ์นั้นอาจเป็นลิงก์สัญลักษณ์ได้ ซึ่งในกรณีนี้ฟังก์ชันต้นฉบับจะต้องดึงข้อมูลเป้าหมายด้วย
ฟังก์ชันจะแสดงเป็นโค้ดโดยอินเทอร์เฟซ SkyFunction
และบริการที่มีให้โดยอินเทอร์เฟซที่เรียกว่า SkyFunction.Environment
ฟังก์ชันต่างๆ ทำได้ดังต่อไปนี้
- ขอการประเมินโหนดอื่นโดยการเรียกใช้
env.getValue
หากโหนดพร้อมใช้งาน ระบบจะส่งคืนค่าของโหนด มิเช่นนั้นจะแสดงผลnull
และฟังก์ชันควรแสดงผลnull
ในกรณีหลัง โหนดที่อ้างอิงกันจะได้รับการประเมิน แล้วจะมีการเรียกใช้เครื่องมือสร้างโหนดเดิมอีกครั้ง แต่ครั้งนี้การเรียกใช้env.getValue
เดียวกันจะแสดงค่าที่ไม่ใช่null
- ขอการประเมินโหนดอื่นๆ หลายโหนดโดยเรียกใช้
env.getValues()
ซึ่งโดยพื้นฐานแล้วก็จะเหมือนกัน เว้นแต่ว่าโหนดที่เกี่ยวข้องจะได้รับการประเมินแบบคู่ขนาน - คำนวณในระหว่างการเรียกใช้
- มีผลข้างเคียง เช่น เขียนไฟล์ลงในระบบไฟล์ แต่ควรระวังเรื่องงาน 2 อย่างที่ว่านี้ไม่ได้เหยียบเท้ากัน โดยทั่วไปแล้ว การเขียนผลข้างเคียง (ในกรณีที่ข้อมูลไหลออกมาจาก Bazel) นั้นใช้ได้ อ่านผลข้างเคียง (ในกรณีที่ข้อมูลไหลเข้าสู่ Bazel โดยไม่ต้องมี Dependency ที่ลงทะเบียน) จะไม่มีผล เนื่องจากข้อมูลดังกล่าวเป็นทรัพยากร Dependency ที่ไม่ได้ลงทะเบียนและอาจทำให้เกิดการเพิ่มขึ้นที่ไม่ถูกต้อง
การใช้งาน SkyFunction
ไม่ควรเข้าถึงข้อมูลด้วยวิธีอื่นๆ นอกเหนือจากการขอทรัพยากร Dependency (เช่น การอ่านระบบไฟล์โดยตรง) เนื่องจากจะทำให้ Bazel ไม่ลงทะเบียนการอ้างอิงข้อมูลในไฟล์ที่อ่าน ซึ่งส่งผลให้มีบิลด์ส่วนเพิ่มที่ไม่ถูกต้อง
เมื่อฟังก์ชันมีข้อมูลเพียงพอที่จะทำงานแล้ว ฟังก์ชันควรแสดงผลค่าที่ไม่ใช่ null
ซึ่งระบุถึงการทำงานเสร็จสมบูรณ์
กลยุทธ์การประเมินนี้มีประโยชน์หลายประการดังนี้
- ความสุจริต หากฟังก์ชันขอเฉพาะข้อมูลอินพุตโดยจะขึ้นอยู่กับโหนดอื่นๆ ด้วย Bazel รับประกันได้ว่าหากสถานะอินพุตเหมือนกัน ระบบจะส่งคืนข้อมูลเดียวกัน หากฟังก์ชันของ Sky ทั้งหมดเป็นแบบกำหนดทิศทาง ก็หมายความว่าสิ่งที่สร้างทั้งหมดจะกำหนดทิศทางด้วยเช่นกัน
- ส่วนเพิ่มที่ถูกต้องและสมบูรณ์แบบ หากมีการบันทึกข้อมูลอินพุตทั้งหมดของฟังก์ชันทั้งหมด Bazel จะนำเฉพาะชุดของโหนดที่ถูกต้องซึ่งกำหนดให้ไม่ถูกต้องเมื่อข้อมูลอินพุตเปลี่ยนแปลงเท่านั้น
- การทำงานพร้อมกัน เนื่องจากฟังก์ชันต่างๆ จะโต้ตอบระหว่างกันด้วยการขอทรัพยากร Dependency เท่านั้น ฟังก์ชันที่ไม่ได้พึ่งพากันและกันจึงจะทำงานพร้อมกันได้และ Bazel สามารถรับประกันได้ว่าผลลัพธ์จะเหมือนกันเสมือนว่าทำงานตามลำดับ
ส่วนเพิ่ม
เนื่องจากฟังก์ชันจะเข้าถึงได้เฉพาะข้อมูลอินพุตเท่านั้น โดยขึ้นอยู่กับโหนดอื่นๆ ทำให้ Bazel สร้างโฟลว์ข้อมูลที่สมบูรณ์จากไฟล์อินพุตไปยังไฟล์เอาต์พุต และใช้ข้อมูลนี้เพื่อสร้างโหนดใหม่เฉพาะที่ต้องสร้างใหม่เท่านั้น นั่นคือการปิดแบบย้อนกลับของชุดไฟล์อินพุตที่มีการเปลี่ยนแปลง
กล่าวอย่างเจาะจงคือ กลยุทธ์ส่วนเพิ่มที่เป็นไปได้มีอยู่ 2 กลยุทธ์ด้วยกัน ได้แก่ กลยุทธ์จากล่างขึ้นบนและกลยุทธ์จากบนลงล่าง วิธีใดจะเหมาะสมที่สุดนั้นขึ้นอยู่กับลักษณะของกราฟทรัพยากร Dependency
ในระหว่างการสร้างโมฆะแบบจากล่างขึ้นบน หลังจากที่สร้างกราฟและทราบชุดอินพุตที่มีการเปลี่ยนแปลงแล้ว โหนดทั้งหมดจะเป็นโมฆะซึ่งอาศัยไฟล์ที่มีการเปลี่ยนแปลงแบบสับเปลี่ยน ซึ่งเหมาะสมที่สุดหากเราทราบว่าจะสร้างโหนดระดับบนสุดเดียวกันอีกครั้ง โปรดทราบว่าการระบุสถานะจากล่างขึ้นบนต้องเรียกใช้
stat()
ในไฟล์อินพุตทั้งหมดของบิลด์ก่อนหน้าเพื่อพิจารณาว่ามีการเปลี่ยนแปลงหรือไม่ ซึ่งคุณจะปรับปรุงได้โดยใช้inotify
หรือกลไกที่คล้ายกันเพื่อเรียนรู้เกี่ยวกับไฟล์ที่มีการเปลี่ยนแปลงในระหว่างการทำให้โหนดระดับบนสุดใช้งานไม่ได้ ระบบจะตรวจสอบการปิดแบบทางอ้อมของโหนดระดับบนสุดและเก็บเฉพาะโหนดเหล่านั้นที่การปิดแบบทางอ้อมไม่มีประสิทธิภาพ วิธีนี้จะดีกว่าหากเราทราบว่ากราฟโหนดปัจจุบันมีขนาดใหญ่ แต่เราต้องการเพียงส่วนเล็กๆ ของกราฟโหนดในบิลด์ถัดไปเท่านั้น นั่นคือ การใช้ไม่ได้จากด้านล่างขึ้นบนจะทำให้กราฟที่ใหญ่กว่าของบิลด์แรกใช้งานไม่ได้ ซึ่งต่างจากการใช้จากด้านบนลงล่าง ซึ่งจะเป็นเพียงกราฟเล็กๆ ของบิลด์ที่ 2
ปัจจุบันเราทำให้ฟังก์ชันจากล่างขึ้นบนใช้ไม่ได้เท่านั้น
หากต้องการหาส่วนเพิ่มเพิ่มเติม เราใช้การตัดการเปลี่ยนแปลง: หากโหนดใช้งานไม่ได้ แต่เมื่อสร้างโหนดใหม่ พบว่าค่าใหม่เหมือนกับค่าเก่า โหนดที่ใช้งานไม่ได้เนื่องจากการเปลี่ยนแปลงในโหนดนี้จะ "กู้คืน"
ซึ่งจะเป็นประโยชน์ เช่น ในกรณีที่มีการเปลี่ยนแปลงความคิดเห็นในไฟล์ 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
เป็นสิ่งสำคัญเนื่องจากในบางกรณี (เช่น การประเมิน glob ของระบบไฟล์ (เช่นsrcs=glob(["*/*.java"])
) เนื้อหาในไฟล์ไม่จำเป็นจริงๆ - DirectoryListingValue ผลลัพธ์โดยประมาณของ
readdir()
ขึ้นอยู่กับFileValue
ที่เชื่อมโยงกับไดเรกทอรี - PackageValue แสดงเวอร์ชันที่แยกวิเคราะห์ของไฟล์ BUILD ขึ้นอยู่กับ
FileValue
ของไฟล์BUILD
ที่เชื่อมโยง และขึ้นอยู่กับDirectoryListingValue
ที่ใช้เพื่อแก้ไข glob ในแพ็กเกจ (โครงสร้างข้อมูลที่แสดงถึงเนื้อหาของไฟล์BUILD
ภายใน) - ConfiguredTargetValue แสดงเป้าหมายที่กำหนดค่า ซึ่งเป็นชุดของชุดการกระทำที่สร้างขึ้นระหว่างการวิเคราะห์เป้าหมายและข้อมูลที่ระบุให้กับเป้าหมายที่กำหนดค่าซึ่งขึ้นอยู่กับเป้าหมายนี้ ขึ้นอยู่กับ
PackageValue
โดยมีเป้าหมายที่เกี่ยวข้อง,ConfiguredTargetValues
ของทรัพยากร Dependency โดยตรง และโหนดพิเศษที่แสดงถึงการกำหนดค่าบิลด์ - ArtifactValue แสดงไฟล์ในบิลด์ ไม่ว่าจะเป็นต้นฉบับหรืออาร์ติแฟกต์เอาต์พุต (อาร์ติแฟกต์แทบจะเทียบเท่ากับไฟล์และใช้เพื่ออ้างถึงไฟล์ในระหว่างดำเนินการตามขั้นตอนของบิลด์จริง) สำหรับไฟล์ต้นฉบับ จะขึ้นอยู่กับ
FileValue
ของโหนดที่เกี่ยวข้อง สำหรับอาร์ติแฟกต์เอาต์พุตจะขึ้นอยู่กับActionExecutionValue
ของการดำเนินการใดก็ตามที่สร้างอาร์ติแฟกต์ - ActionExecutionValue แสดงถึงการดำเนินการ ขึ้นอยู่กับ
ArtifactValues
ของไฟล์อินพุต การทำงานที่ดำเนินการอยู่นั้นอยู่ในคีย์ Sky ซึ่งขัดกับแนวคิดที่ว่า คีย์ Sky ควรมีขนาดเล็ก เรากำลังหาทางแก้ไขความคลาดเคลื่อนนี้ (โปรดทราบว่าระบบจะไม่ได้ใช้ActionExecutionValue
และArtifactValue
หากเราไม่ได้เรียกใช้เฟสการดำเนินการใน Skyframe)