การประเมินแบบขนานและรูปแบบการเพิ่มขึ้นของ 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
ไม่ควรเข้าถึงข้อมูลด้วยวิธีอื่นๆ นอกเหนือจากการขอทรัพยากร Dependency (เช่น การอ่านระบบไฟล์โดยตรง) เนื่องจากจะทำให้ 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
ของทรัพยากร Dependency โดยตรง และโหนดพิเศษที่แสดงถึงการกำหนดค่าบิลด์ - ArtifactValue แสดงไฟล์ในบิลด์ ไม่ว่าจะเป็นแหล่งที่มาหรืออาร์ติแฟกต์เอาต์พุต (อาร์ติแฟกต์เกือบจะเทียบเท่ากับไฟล์ และใช้สําหรับอ้างอิงไฟล์ในระหว่างการดําเนินการจริงของขั้นตอนการสร้าง) สำหรับไฟล์ต้นฉบับ
FileValue
จะขึ้นอยู่กับโหนดที่เชื่อมโยง ส่วนสำหรับอาร์ติแฟกต์เอาต์พุตActionExecutionValue
จะขึ้นอยู่กับการดำเนินการใดก็ตามที่สร้างอาร์ติแฟกต์ - ActionExecutionValue. แสดงถึงการดำเนินการ ขึ้นอยู่กับ
ArtifactValues
ของไฟล์อินพุต การทำงานที่ดำเนินการอยู่นั้นอยู่ในคีย์ Sky ซึ่งขัดกับแนวคิดที่ว่า คีย์ Sky ควรมีขนาดเล็ก เรากำลังหาทางแก้ไขความคลาดเคลื่อนนี้ (โปรดทราบว่าระบบจะไม่ได้ใช้ActionExecutionValue
และArtifactValue
หากเราไม่ได้เรียกใช้เฟสการดำเนินการบน Skyframe)