สกายเฟรม

วันที่ รายงานปัญหา ดูแหล่งที่มา ตอนกลางคืน · 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 ระดับบนสุด จากนั้นฟังก์ชันจะขอการประเมินโหนดที่ต้องใช้ในการประเมินโหนดระดับบนสุด ซึ่งส่งผลให้เกิดการเรียกใช้ฟังก์ชันอื่นๆ เป็นเช่นนี้ไปเรื่อยๆ จนกระทั่งถึงโหนด 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)