สกายเฟรม

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 8.0 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

การประเมินแบบขนานและรูปแบบการเพิ่มขึ้นของ 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)