เจาะลึกการแสดงภาพNG: BlinkNG

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

Blink หมายถึงการใช้งานแพลตฟอร์มเว็บของ Chromium และครอบคลุมทุกขั้นตอนของการแสดงผลก่อนทำการประสาน ซึ่งนำไปสู่การคอมมิตคอมโพสิต คุณสามารถอ่านเพิ่มเติมเกี่ยวกับสถาปัตยกรรมการแสดงผลแบบกะพริบในบทความก่อนหน้าในซีรีส์นี้

Blink เริ่มต้นชีวิตจาก WebKit ซึ่งเป็นส้อมของ KHTML ตั้งแต่ปี 1998 โค้ดนี้มีโค้ดที่เก่าที่สุด (และสำคัญที่สุด) บางส่วนใน Chromium และภายในปี 2014 โค้ดนี้แสดงให้เห็นอายุของแอปอย่างชัดเจน ในปีนั้น เราได้เริ่มจัดทำชุดโครงการอันทะเยอทะยานภายใต้แบนเนอร์สิ่งที่เราเรียกว่า BlinkNG โดยมีเป้าหมายเพื่อแก้ไขข้อบกพร่องที่มีมาอย่างยาวนานในองค์กรและโครงสร้างของโค้ด Blink บทความนี้จะพาไปสำรวจ BlinkNG และโปรเจ็กต์ต่างๆ ที่เป็นส่วนหนึ่งของ BlinkNG: เหตุผลที่เราทำ สิ่งที่พวกเขาประสบความสำเร็จ หลักการชี้นำที่กำหนดแนวทางการออกแบบ และโอกาสในการปรับปรุงในอนาคตที่มี

ไปป์ไลน์การแสดงภาพก่อนและหลัง BlinkNG

กำลังแสดงผล Pre-NG

ไปป์ไลน์การแสดงภาพภายใน Blink มักจะแบ่งเป็นช่วงๆ (สไตล์, เลย์เอาต์, สี และอื่นๆ) แต่อุปสรรคด้านนามธรรมนั้นรั่วไหล หรือพูดกว้างๆ ก็คือข้อมูลที่เกี่ยวข้องกับการแสดงภาพจะประกอบด้วยออบเจ็กต์ที่มีอายุการใช้งานยาวนานและเปลี่ยนแปลงได้ วัตถุเหล่านี้สามารถได้รับการแก้ไขได้ตลอดเวลา และมักมีการนำกลับมาใช้ใหม่และใช้ซ้ำโดยการอัปเดตการแสดงผลติดต่อกัน คงเป็นไปไม่ได้ที่จะตอบคำถามง่ายๆ ได้อย่างถูกต้องแม่นยำอย่างเช่น

  • จำเป็นต้องอัปเดตเอาต์พุตของรูปแบบ เลย์เอาต์ หรือสีไหม
  • ข้อมูลเหล่านี้จะได้ "ขั้นสุดท้าย" เมื่อใด
  • เมื่อใดที่คุณจะสามารถแก้ไขข้อมูลเหล่านี้
  • ระบบจะลบออบเจ็กต์นี้เมื่อใด

ซึ่งมีตัวอย่างมากมายดังนี้

รูปแบบจะสร้าง ComputedStyle ตามสไตล์ชีต แต่ ComputedStyle นั้นเปลี่ยนแปลงไม่ได้ ในบางกรณี อาจมีการแก้ไขโดยขั้นตอนภายหลังของไปป์ไลน์

Style จะสร้างแผนผังของ LayoutObject จากนั้น layout จะทำคำอธิบายประกอบให้กับวัตถุเหล่านั้นด้วยข้อมูลขนาดและตำแหน่ง ในบางกรณี เลย์เอาต์อาจเป็นการแก้ไขโครงสร้างแผนผังด้วย ไม่มีการแยกระหว่างอินพุตและเอาต์พุตของเลย์เอาต์อย่างชัดเจน

Style จะสร้างโครงสร้างข้อมูลเสริมที่กำหนดวิธีการของการประกอบ และมีการแก้ไขโครงสร้างข้อมูลเหล่านั้นทุกระยะหลังจากสไตล์

ในระดับที่ต่ำกว่า การแสดงผลประเภทข้อมูลมักจะประกอบด้วยต้นไม้เฉพาะทาง (เช่น แผนผัง DOM, แผนผังรูปแบบ, แผนผังเลย์เอาต์, แผนผังพร็อพเพอร์ตี้ Paint) และเฟสการแสดงผลจะใช้เป็นการเดินบนต้นไม้แบบวนซ้ำ ตามหลักการแล้ว การเดินบนต้นไม้ควรมีอยู่ นั่นคือ เมื่อประมวลผลโหนดต้นไม้หนึ่งๆ เราไม่ควรเข้าถึงข้อมูลใดๆ นอกโครงสร้างย่อยที่รูทที่โหนดนั้น การใช้ Pre-RenderingNG เป็นไปอย่างไม่เป็นความจริง ต้นไม้เดินผ่านข้อมูลที่มีการเข้าถึงบ่อยครั้งจากระดับบนของโหนดที่ประมวลผล ทำให้ระบบเปราะบางและเกิดข้อผิดพลาดได้ง่าย และอาจเป็นไปไม่ได้ที่จะเริ่มเดินบนต้นไม้จากทุกที่ ยกเว้นรากของต้นไม้

สุดท้าย มีจุดขึ้นเป็นจำนวนมากในไปป์ไลน์การแสดงภาพที่โรยตัวตลอดโค้ด ได้แก่ เลย์เอาต์แบบบังคับที่เรียกใช้โดย JavaScript, การอัปเดตบางส่วนที่ทำงานระหว่างการโหลดเอกสาร, การบังคับให้อัปเดตเพื่อเตรียมพร้อมสำหรับการกำหนดเป้าหมายเหตุการณ์, การอัปเดตตามกำหนดการที่ขอโดยระบบแสดงผล และ API พิเศษที่แสดงเฉพาะโค้ดทดสอบ เป็นต้น นอกจากนี้ยังมีเส้นทางที่เกิดซ้ำและเส้นทางซ้ำอีก 2-3 เส้นทางในไปป์ไลน์การแสดงผล (นั่นคือการข้ามไปยังจุดเริ่มต้นของขั้นตอนหนึ่งจากตรงกลางของอีกระยะ) ในหน่วยความจำแต่ละรายการเหล่านี้มีลักษณะการทำงานที่แปลกประหลาด และในบางกรณี เอาต์พุตของการแสดงผลจะขึ้นอยู่กับลักษณะการทริกเกอร์การอัปเดตการแสดงผล

สิ่งที่เราเปลี่ยนแปลง

BlinkNG ประกอบด้วยโครงการย่อยมากมายทั้งขนาดเล็กและใหญ่ ซึ่งมีเป้าหมายร่วมกันในการกำจัดความบกพร่องทางสถาปัตยกรรมที่อธิบายไว้ก่อนหน้านี้ โปรเจ็กต์เหล่านี้มีหลักการชี้นำ 2-3 ข้อที่ออกแบบมาเพื่อทำให้ไปป์ไลน์การแสดงภาพมีความคล้ายคลึงกับไปป์ไลน์จริงมากขึ้น

  • จุดเข้าที่เป็นแบบเดียวกัน: เราควรป้อนไปป์ไลน์ที่จุดเริ่มต้นเสมอ
  • ระยะฟังก์ชัน: แต่ละขั้นตอนควรมีอินพุตและเอาต์พุตที่กำหนดไว้อย่างชัดเจน และลักษณะการทำงานควรเป็นแบบใช้งานได้ กล่าวคือ กำหนดได้และทำซ้ำได้ และเอาต์พุตควรขึ้นอยู่กับอินพุตที่กำหนดเท่านั้น
  • อินพุตคงที่: อินพุตของระยะใดๆ ควรคงที่อย่างมีประสิทธิภาพในขณะที่พื้นที่งานกำลังทำงาน
  • เอาต์พุตที่เปลี่ยนแปลงไม่ได้: เมื่อเสร็จสิ้นขั้นตอนแล้ว เอาต์พุตของสถานะจะต้องไม่เปลี่ยนแปลงสำหรับช่วงเวลาที่เหลือของการอัปเดตการแสดงผล
  • ความสอดคล้องของจุดตรวจสอบ: ในช่วงท้ายของแต่ละขั้นตอน ข้อมูลการแสดงผลที่สร้างขึ้นจนถึงปัจจุบันควรอยู่ในสถานะเดียวกัน
  • การกรองงานที่ซ้ำกันออก: คำนวณงานแต่ละอย่างเพียงครั้งเดียว

รายชื่อโปรเจ็กต์ย่อยทั้งหมดของ BlinkNG จะทำให้น่าเบื่อ แต่ต่อไปนี้คือผลลัพธ์บางอย่าง

วงจรของเอกสาร

คลาส DocumentLifecycle จะติดตามความคืบหน้าผ่านไปป์ไลน์การแสดงผล ซึ่งทำให้เราตรวจสอบพื้นฐานที่จะบังคับใช้ค่าคงที่ที่ระบุไว้ก่อนหน้านี้ได้ เช่น

  • หากเรากำลังแก้ไขพร็อพเพอร์ตี้ ComputedStyle วงจรชีวิตของเอกสารต้องเป็น kInStyleRecalc
  • ถ้าสถานะ DocumentLifecycle เป็น kStyleClean หรือใหม่กว่านั้น NeedsStyleRecalc() จะต้องแสดงผล false สำหรับโหนดที่แนบ
  • เมื่อเข้าสู่ช่วงอายุการใช้งาน paint สถานะของวงจรจะต้องเป็น kPrePaintClean

ในระหว่างการติดตั้งใช้งาน BlinkNG เรากำจัดเส้นทางโค้ดที่ละเมิดตัวแปรเหล่านี้อย่างเป็นระบบ และเพิ่มการยืนยันอื่นๆ ทั่วทั้งโค้ดเพื่อให้แน่ใจว่าจะไม่เกิดปัญหาซ้ำ

ถ้าคุณเคยตกหลุมพรางเพื่อดูโค้ดการแสดงภาพในระดับต่ำ คุณอาจถามตัวเองว่า "ฉันมาอยู่ที่นี่ได้อย่างไร" ดังที่กล่าวไว้ก่อนหน้านี้ ความสามารถในการเข้าสู่ไปป์ไลน์การแสดงผลมีหลายวิธี ก่อนหน้านี้ เส้นทางดังกล่าวรวมถึงเส้นทางการโทรที่เกิดซ้ำและซ้ำอยู่ และเป็นส่วนที่เราเข้าสู่ไปป์ไลน์ในระยะกลาง แทนที่จะเริ่มต้นตั้งแต่ต้น ในช่วงที่ BlinkNG เราวิเคราะห์เส้นทางการโทรเหล่านี้ และพบว่าทั้งหมดนี้สามารถลดทอนได้เป็นสถานการณ์พื้นฐาน 2 กรณีดังนี้

  • ข้อมูลการแสดงผลทั้งหมดจำเป็นต้องได้รับการอัปเดต เช่น เมื่อสร้างพิกเซลใหม่สำหรับโฆษณาแบบดิสเพลย์ หรือทำการทดสอบ Hit สำหรับการกำหนดเป้าหมายเหตุการณ์
  • เราต้องการค่าที่เป็นปัจจุบันสำหรับการค้นหาที่เจาะจงซึ่งสามารถให้คำตอบได้โดยไม่ต้องอัปเดตข้อมูลการแสดงผลทั้งหมด ซึ่งรวมถึงการค้นหา JavaScript ส่วนใหญ่ เช่น node.offsetTop

ขณะนี้มีเพียง 2 จุดแรกที่เข้าสู่ไปป์ไลน์การแสดงผล ซึ่งสอดคล้องกับทั้ง 2 สถานการณ์นี้ ระบบได้นำเส้นทางโค้ดสำหรับผู้เข้าร่วมออกหรือเปลี่ยนโครงสร้างภายในโค้ดแล้ว และจะเข้าสู่ไปป์ไลน์โดยเริ่มตั้งแต่ระยะกลางไม่ได้อีกต่อไป ซึ่งช่วยขจัดข้อสงสัยเกี่ยวกับเวลาและวิธีการอัปเดตภาพได้ ทำให้สามารถให้เหตุผลเกี่ยวกับการทำงานของระบบได้ง่ายขึ้น

รูปแบบไปป์ไลน์ เลย์เอาต์ และสีทาก่อนทาสี

โดยรวมแล้ว เฟสการแสดงผลก่อน paint จะมีสาเหตุดังต่อไปนี้

  • เรียกใช้อัลกอริทึมการต่อแบบ Cascade ของสไตล์เพื่อคำนวณคุณสมบัติสุดท้ายของโหนด DOM
  • กำลังสร้างแผนผังลำดับชั้นที่แสดงลำดับชั้นของช่องของเอกสาร
  • กำลังกำหนดข้อมูลขนาดและตำแหน่งสำหรับทุกช่อง
  • การปัดเศษหรือสแนปเรขาคณิตของพิกเซลย่อยให้เป็นขอบเขตพิกเซลทั้งหมดสำหรับการวาดภาพ
  • การกำหนดคุณสมบัติของเลเยอร์แบบผสม (การเปลี่ยนภาพ ตัวกรอง ความทึบแสง หรืออื่นๆ ที่เร่งโดยใช้ GPU ได้)
  • พิจารณาว่าเนื้อหาใดเปลี่ยนแปลงตั้งแต่เฟสการลงสีก่อนหน้า และต้องทาสีหรือทาสีใหม่ (การระบายสี)

รายการนี้ไม่มีการเปลี่ยนแปลง แต่ก่อนที่ BlinkNG ได้ทำงานนี้เสร็จด้วยวิธีเฉพาะกิจ โดยกระจายไปในหลายๆ ระยะการแสดงผล รวมทั้งมีฟังก์ชันการทำงานที่ซ้ำกันมากมายและไม่มีประสิทธิภาพในตัว ตัวอย่างเช่น เฟส style มีหน้าที่หลักในการคำนวณคุณสมบัติสุดท้ายของโหนดมาตลอด แต่มีกรณีพิเศษ 2-3 กรณีที่เราไม่ได้ระบุค่าพร็อพเพอร์ตี้ของรูปแบบสุดท้ายจนกว่าขั้นตอน style จะเสร็จสมบูรณ์ เรายังไม่มีจุดที่เป็นทางการหรือสามารถบังคับใช้ได้ในขั้นตอนการแสดงภาพ ซึ่งเราสามารถพูดได้อย่างแน่ชัดว่า ข้อมูลรูปแบบนั้นครบถ้วนสมบูรณ์และเปลี่ยนแปลงไม่ได้

ตัวอย่างที่ดีอีกตัวอย่างหนึ่งของปัญหาก่อน BlinkNG คือการทำให้สีใช้งานไม่ได้ ก่อนหน้านี้ การระบุว่าสีไม่ถูกต้องเกิดขึ้นตลอดระยะการแสดงภาพทั้งหมดที่นำไปสู่การระบายสี ในการแก้ไขรูปแบบหรือโค้ดเลย์เอาต์ เป็นเรื่องที่ยากที่จะทราบว่าต้องทำการเปลี่ยนแปลงอะไรในตรรกะการระบุว่าไม่ถูกต้องหรือไม่ และการทำผิดก็ง่ายที่จะนำไปสู่ข้อบกพร่องที่มีข้อผิดพลาดน้อยเกินไปหรือมากเกินไป คุณสามารถอ่านเพิ่มเติมเกี่ยวกับความซับซ้อนของระบบการทำให้สีเก่าไม่ถูกต้องได้ในบทความจากซีรีส์นี้ ที่มีไว้สำหรับ LayoutNG โดยเฉพาะ

การสแนปเรขาคณิตของเลย์เอาต์พิกเซลย่อยกับขอบเขตพิกเซลทั้งหมดสำหรับการวาดภาพ เป็นตัวอย่างของกรณีที่เรามีฟังก์ชันการทำงานเดียวกันหลายตัว และได้ทำงานซ้ำซ้อนเป็นจำนวนมาก ก่อนหน้านี้ระบบ Paint ใช้โค้ดการสแนปพิกเซล 1 เส้นทาง และเส้นทางโค้ดแยกต่างหากทั้งหมดซึ่งใช้เมื่อเราต้องการการคำนวณพิกัดการสแนปพิกเซลแบบครั้งเดียวแบบครั้งเดียวจากโค้ด Paint แน่นอนว่าการติดตั้งใช้งานแต่ละรายการมีข้อบกพร่องของตัวเอง และผลลัพธ์ที่ได้ก็ไม่ตรงกันเสมอไป เนื่องจากไม่มีการแคชข้อมูลนี้ บางครั้งระบบจึงทำการคำนวณแบบเดียวกันซ้ำๆ เป็นอีกแรงตึงของประสิทธิภาพการทำงาน

ต่อไปนี้เป็นโปรเจ็กต์สำคัญที่ช่วยขจัดความบกพร่องทางสถาปัตยกรรมของเฟสการแสดงผลก่อนการลงสี

Project Squad: การดำเนินกระบวนการภายในระยะรูปแบบ

โครงการนี้จัดการกับความบกพร่องหลักๆ 2 ประการในขั้นตอนการจัดรูปแบบ ซึ่งทำให้ไม่สามารถจัดวางองค์ประกอบได้อย่างไม่มีปัญหา ดังนี้

เฟสรูปแบบมีเอาต์พุตหลัก 2 แบบ ได้แก่ ComputedStyle ซึ่งเป็นผลมาจากการเรียกใช้อัลกอริทึมแบบ Cascade ของ CSS บนต้นไม้ DOM และแผนผังของ LayoutObjects ซึ่งจะกำหนดลำดับการดำเนินการสำหรับเฟสของเลย์เอาต์ โดยหลักการแล้ว การเรียกใช้อัลกอริทึมแบบ Cascade ควรเกิดขึ้นก่อนการสร้างแผนผังเลย์เอาต์เท่านั้น แต่ก่อนหน้านี้ การดำเนินการทั้ง 2 อย่างนี้แทรกสลับกัน Project Squad สามารถแยกส่วน 2 สิ่งนี้ออกเป็นเฟสที่แตกต่างกันตามลำดับ

ก่อนหน้านี้ ComputedStyle ไม่ได้รับค่าสุดท้ายเสมอไประหว่างการคำนวณรูปแบบใหม่ มี 2-3 สถานการณ์ที่ ComputedStyle ได้รับการอัปเดตในขั้นตอนไปป์ไลน์หลังจากนี้ Project Squad เปลี่ยนโครงสร้างภายในโค้ดเส้นทางโค้ดเหล่านี้ได้สำเร็จ เพื่อไม่ให้มีการแก้ไข ComputedStyle หลังจากเฟสรูปแบบ

LayoutNG: ไปป์ไลน์ของเลย์เอาต์

โปรเจ็กต์โมเมนต์นี้ ซึ่งเป็นหนึ่งในพื้นฐานที่สำคัญของ RenderingNG ก็คือการเขียนเฟสการแสดงผลเลย์เอาต์ใหม่ทั้งหมด เราจะไม่ให้ความสำคัญกับทั้งโครงการ แต่โครงการ BlinkNG โดยรวมก็มีด้านที่โดดเด่นอยู่ 2-3 ด้าน

  • ก่อนหน้านี้ เฟสเลย์เอาต์ได้รับแผนผังของ LayoutObject ที่สร้างโดยเฟสรูปแบบ และใส่คำอธิบายประกอบแผนผังด้วยข้อมูลขนาดและตำแหน่ง จึงไม่มีการแยกอินพุตออกจากเอาต์พุตอย่างชัดเจน LayoutNG เปิดตัวFragment Tree ซึ่งเป็นเอาต์พุตหลักแบบอ่านอย่างเดียวของเลย์เอาต์และทำหน้าที่เป็นอินพุตหลักสำหรับเฟสการแสดงผลที่ตามมา
  • LayoutNG นำพร็อพเพอร์ตี้การควบคุมมาไว้ในเลย์เอาต์: เมื่อคำนวณขนาดและตำแหน่งของ LayoutObject ที่ระบุ เราจะไม่ดูภายนอกแผนผังย่อยที่รูทที่ออบเจ็กต์นั้นอีกต่อไป ข้อมูลทั้งหมดที่จำเป็นในการอัปเดตการจัดวางวัตถุที่กำหนดจะได้รับการคำนวณล่วงหน้าและจัดเตรียมเป็นอินพุตแบบอ่านอย่างเดียวสำหรับอัลกอริทึม
  • ก่อนหน้านี้ มีบางกรณีที่อัลกอริทึมเลย์เอาต์ใช้งานไม่ได้อย่างเคร่งครัด ซึ่งเป็นผลของอัลกอริทึมขึ้นอยู่กับการอัปเดตเลย์เอาต์ครั้งล่าสุด LayoutNG ขจัดกรณีเหล่านี้ออกแล้ว

ขั้นตอนการก่อนลงสี

ก่อนหน้านี้ ยังไม่มีเฟสก่อนการลงสีอย่างเป็นทางการ แต่เป็นกระบวนการหลังการออกแบบเท่านั้น ช่วงก่อนลงสี เกิดจากการตระหนักว่ามีฟังก์ชันที่เกี่ยวข้อง 2-3 ฟังก์ชันที่นำมาใช้ได้ดีที่สุดเมื่อเป็นการส่งต่อผ่านโครงสร้างของเลย์เอาต์อย่างเป็นระบบหลังจากที่เลย์เอาต์เสร็จสมบูรณ์ ที่สำคัญที่สุด:

  • การทําให้สีไม่ถูกต้อง: การทําให้สีไม่ถูกต้องในระหว่างเลย์เอาต์นั้นทําได้ยากมาก เมื่อเรามีข้อมูลไม่ครบถ้วน การตรวจสอบที่ถูกต้องสามารถทำได้ง่ายขึ้นมากและมีประสิทธิภาพมากหากแบ่งออกเป็น 2 กระบวนการที่แตกต่างกัน ได้แก่ ระหว่างรูปแบบและเลย์เอาต์ สามารถทำเครื่องหมายเนื้อหาด้วยธงบูลีนง่ายๆ ว่า "อาจจำเป็นต้องทำให้สีใช้งานไม่ได้" ระหว่างการเดินแผนผังก่อนระบายสี เราจะตรวจสอบธงเหล่านี้และปัญหาเหล่านี้ตามความจำเป็น
  • การสร้างต้นไม้สำหรับพร็อพเพอร์ตี้ของสี: ขั้นตอนที่อธิบายไว้อย่างละเอียดมากขึ้น
  • การคำนวณและการบันทึกตำแหน่งของการแสดงผลแบบพิกเซล: ผลลัพธ์ที่บันทึกไว้สามารถใช้โดยเฟสของ Paint และโค้ดดาวน์สตรีมที่ต้องการ โดยไม่มีการคำนวณซ้ำซ้อน

ต้นไม้ในคุณสมบัติ: เรขาคณิตที่สม่ำเสมอ

มีการแนะนำพร็อพเพอร์ตี้ต้นไม้ในตอนต้นของ RenderingNG ในการจัดการกับความซับซ้อนของการเลื่อน ซึ่งบนเว็บมีโครงสร้างที่แตกต่างจากเอฟเฟกต์ภาพประเภทอื่นๆ ทั้งหมด ก่อนโครงสร้างต้นไม้ องค์ประกอบ compositor ของ Chromium ใช้ "เลเยอร์" เดียว ลำดับชั้นที่จะแสดงความสัมพันธ์ทางเรขาคณิตของเนื้อหาแบบผสม แต่ลดลงอย่างรวดเร็วเนื่องจากความซับซ้อนของฟีเจอร์ทั้งหมด เช่น position:fixed เริ่มเด่นชัดแล้ว ลำดับชั้นของเลเยอร์ขยายตัวชี้ที่ไม่ใช่ตำแหน่งในเครื่องมากขึ้นซึ่งบ่งชี้ว่า "ระดับบนสุดของการเลื่อน" หรือ "ตัดคลิปผู้ปกครอง" ของเลเยอร์หนึ่ง และก่อนหน้านี้ ก็เข้าใจโค้ดได้ยากมาก

แผนผังพร็อพเพอร์ตี้แก้ไขได้โดยแสดงลักษณะการเลื่อนและคลิปเพิ่มเติมของเนื้อหาแยกจากเอฟเฟกต์ภาพอื่นๆ ทั้งหมด ซึ่งทำให้สามารถจำลองโครงสร้างภาพและการเลื่อนที่แท้จริงของเว็บไซต์ได้อย่างถูกต้อง ถัดไป "ทั้งหมด" ที่เราต้องทำคือนำอัลกอริทึมมาอยู่บนโครงสร้างพร็อพเพอร์ตี้ เช่น การเปลี่ยนแปลงพื้นที่หน้าจอของเลเยอร์แบบผสม หรือการกำหนดเลเยอร์ที่ไม่เลื่อนและเลเยอร์ใดที่ไม่เลื่อน

อันที่จริง เราพบว่ามีอีกหลายสถานที่ในรหัสที่มีการถามคำถามเกี่ยวกับเรขาคณิตที่คล้ายกัน (โพสต์โครงสร้างข้อมูลคีย์มีรายการที่สมบูรณ์มากกว่า) โซลูชันหลายรายการมีการติดตั้งเหมือนกับโค้ดของคอมโพสิต ล้วนมีข้อบกพร่องต่างกัน และไม่มีการสร้างแบบจำลองโครงสร้างเว็บไซต์ที่แท้จริงอย่างเหมาะสม หลังจากนั้น วิธีแก้ปัญหาก็ชัดเจนคือ รวมอัลกอริทึมทางเรขาคณิตทั้งหมดไว้ในที่เดียว แล้วเปลี่ยนโครงสร้างภายในโค้ดทั้งหมดเพื่อใช้งาน

ในทางกลับกัน อัลกอริทึมเหล่านี้ทั้งหมดก็ขึ้นอยู่กับต้นไม้พร็อพเพอร์ตี้ ซึ่งเป็นเหตุผลที่โครงสร้างข้อมูลคีย์ กล่าวคือเป็นโครงสร้างที่ใช้ตลอดทั้งไปป์ไลน์ของ RenderingNG ดังนั้น ในการบรรลุเป้าหมายของโค้ดเรขาคณิตแบบรวมศูนย์ เราต้องแนะนำแนวคิดของต้นไม้แห่งคุณสมบัตินี้ตั้งแต่เนิ่นๆ ในไปป์ไลน์ โดยการลงสีล่วงหน้า และเปลี่ยน API ทั้งหมดที่ตอนนี้ต้องอาศัยการลงสีอยู่ให้ต้องทํางานก่อนการลงสีก่อนที่จะทํางาน

เรื่องราวนี้เป็นเพียงอีกแง่มุมหนึ่งของรูปแบบการเปลี่ยนโครงสร้างภายในโค้ดของ BlinkNG นั่นคือ ระบุการคำนวณที่สำคัญ เปลี่ยนโครงสร้างภายในโค้ดเพื่อหลีกเลี่ยงการสร้างซ้ำ และสร้างขั้นตอนไปป์ไลน์ที่ชัดเจนซึ่งสร้างโครงสร้างข้อมูลป้อนอินพุต เราคำนวณโครงสร้างทรัพย์สิน ณ จุดที่มีข้อมูลที่จำเป็นครบถ้วน และรับรองว่าโครงสร้างพร็อพเพอร์ตี้จะไม่เปลี่ยนแปลงขณะที่ระยะการแสดงผลภายหลังทำงานอยู่

การผสมสีหลังการพ่นสี: สีสำหรับการเดินท่อและการประกอบ

การสร้างเลเยอร์เป็นกระบวนการพิจารณาว่าเนื้อหา DOM ใดอยู่ในเลเยอร์ที่ทำการ Composite ของตัวเอง (ซึ่งจะแสดงพื้นผิว GPU) ก่อน RenderingNG การแบ่งเลเยอร์จะทํางานก่อนการลงสี ไม่ใช่หลังจากการแสดงผล (ดูไปป์ไลน์ปัจจุบันที่นี่ โปรดสังเกตการเปลี่ยนแปลงลําดับ) เราจะตัดสินใจก่อนว่าส่วนใดของ DOM เข้าไปในเลเยอร์ที่ทำการ Composite จากนั้นจึงสร้างรายการแสดงผลสำหรับพื้นผิวเหล่านั้น โดยธรรมชาติแล้ว การตัดสินใจจะขึ้นอยู่กับปัจจัยต่างๆ เช่น องค์ประกอบ DOM ใดที่เคลื่อนไหวหรือเลื่อน หรือมีการแปลงแบบ 3 มิติ และองค์ประกอบที่วาดด้านบน

ซึ่งก่อให้เกิดปัญหาใหญ่ เนื่องจากโค้ดต้องการการขึ้นต่อกันแบบวงกลมในโค้ดมากขึ้นหรือน้อยลง ซึ่งเป็นปัญหาใหญ่สำหรับไปป์ไลน์การแสดงผล ลองดูเหตุผลจากตัวอย่าง สมมติว่าเราจำเป็นต้องทำให้ข้อมูลไม่ถูกต้อง (หมายความว่าเราต้องวาดรายการแสดงผลอีกครั้งแล้วแรสเตอร์อีกครั้ง) ความจำเป็นที่จะทำให้เป็นโมฆะอาจมาจากการเปลี่ยนแปลงใน DOM หรือจากรูปแบบหรือเลย์เอาต์ที่เปลี่ยนแปลง แต่แน่นอนว่าเราอยากให้เอาเฉพาะส่วนที่มีการเปลี่ยนแปลงจริงมาเป็นโมฆะ ซึ่งหมายถึงการค้นหาว่าเลเยอร์ที่ประกอบขึ้นเป็นเลเยอร์ใดที่ได้รับผลกระทบ แล้วทำให้รายการที่แสดงของเลเยอร์เหล่านั้นบางส่วนหรือทั้งหมดไม่ถูกต้อง

ซึ่งหมายความว่าการเป็นโมฆะขึ้นอยู่กับ DOM, รูปแบบ, เลย์เอาต์ และการตัดสินใจเกี่ยวกับการกำหนดเลเยอร์ในอดีต (ที่ผ่านมา: ความหมายของเฟรมที่แสดงผลก่อนหน้า) แต่การแบ่งเลเยอร์ในปัจจุบันก็ขึ้นอยู่กับสิ่งเหล่านั้นทั้งหมดเช่นกัน และเนื่องจากเราไม่มีสำเนาข้อมูลการสร้างเลเยอร์ทั้งหมดสองสำเนา จึงยากที่จะบอกความแตกต่างระหว่างการตัดสินใจเกี่ยวกับการสร้างเลเยอร์ในอดีตและอนาคต ดังนั้นเราจึงได้โค้ดจำนวนมากที่มีเหตุผลประกอบกัน ซึ่งบางครั้งก็ทำให้เกิดโค้ดที่ไม่ถูกต้องหรือไม่ถูกต้อง หรือแม้แต่ความขัดข้องหรือปัญหาด้านความปลอดภัยถ้าเราไม่ระมัดระวัง

เพื่อจัดการกับสถานการณ์นี้ ก่อนหน้านี้เราแนะนำแนวคิดของออบเจ็กต์ DisableCompositingQueryAsserts โดยส่วนใหญ่ หากโค้ดพยายามสืบค้นการตัดสินใจเกี่ยวกับการกำหนดเลเยอร์ในอดีต จะทำให้การยืนยันล้มเหลวและเบราว์เซอร์ขัดข้องหากอยู่ในโหมดแก้ไขข้อบกพร่อง ซึ่งช่วยหลีกเลี่ยงการแสดงข้อบกพร่องใหม่ๆ ได้ และในแต่ละกรณีที่โค้ดจำเป็นต้องใช้เพื่อค้นหาการตัดสินใจเกี่ยวกับการสร้างเลเยอร์ในอดีต เราจะใส่โค้ดเพื่ออนุญาตโค้ดโดยการจัดสรรออบเจ็กต์ DisableCompositingQueryAsserts

ในอนาคตเราวางแผนที่จะกำจัดออบเจ็กต์ DisableCompositingQueryAssert ในเว็บไซต์ที่เรียกใช้ทุกรายการ แล้วประกาศว่าโค้ดดังกล่าวปลอดภัยและถูกต้อง แต่สิ่งที่เราค้นพบคือ การเรียกจำนวนมากไม่สามารถนำออกได้ ตราบใดที่การทำเลเยอร์เกิดขึ้นก่อนการลงสี (ในที่สุดเราก็สามารถนำออกได้เมื่อเร็วๆ นี้เท่านั้น) นี่คือเหตุผลแรกที่พบสำหรับโปรเจ็กต์ Composite After Paint สิ่งที่เราได้เรียนรู้คือแม้ว่าคุณจะมีเฟสของไปป์ไลน์ที่กำหนดไว้อยู่แล้วสำหรับการดำเนินการ แต่หากอยู่ในตำแหน่งที่ไม่ถูกต้องในไปป์ไลน์ คุณก็จะติดขัดได้ในที่สุด

เหตุผลที่ 2 สำหรับโครงการ Composite After Paint คือข้อบกพร่องของการจัดองค์ประกอบพื้นฐาน วิธีหนึ่งในการระบุข้อบกพร่องนี้ก็คือเอลิเมนต์ DOM ไม่ใช่การแทนแบบ 1:1 ที่ดีสำหรับรูปแบบการสร้างเลเยอร์ที่มีประสิทธิภาพหรือสมบูรณ์สำหรับเนื้อหาหน้าเว็บ และเนื่องจากการประสานเกิดขึ้นก่อนการลงสี องค์ประกอบนี้จึงขึ้นอยู่กับองค์ประกอบ DOM มากขึ้นหรือน้อยลง โดยไม่แสดงรายการหรือต้นไม้คุณสมบัติ ซึ่งคล้ายกับเหตุผลที่เรานำเสนอต้นไม้พร็อพเพอร์ตี้มาก และเช่นเดียวกับต้นไม้พร็อพเพอร์ตี้ โซลูชันนี้จะอธิบายโดยตรงหากคุณทราบระยะที่เหมาะสมของไปป์ไลน์ เรียกใช้ในเวลาที่เหมาะสม และมอบโครงสร้างข้อมูลคีย์ที่ถูกต้อง และเช่นเดียวกับแผนผังพร็อพเพอร์ตี้ นี่เป็นโอกาสที่ดีในการรับประกันว่าเมื่อเฟสการลงสีเสร็จสมบูรณ์แล้ว เอาต์พุตจะไม่สามารถเปลี่ยนแปลงได้สำหรับเฟสไปป์ไลน์ที่ตามมาทั้งหมด

ประโยชน์

ดังที่คุณเห็น ไปป์ไลน์การแสดงผลที่กำหนดไว้เป็นอย่างดีจะให้ประโยชน์ในระยะยาวอย่างมหาศาล มีสิ่งต่างๆ มากกว่าที่คุณคิด:

  • มีความเสถียรที่ดีขึ้นมาก: ข้อนี้ค่อนข้างตรงไปตรงมา โค้ดที่ดูง่ายขึ้นซึ่งมีอินเทอร์เฟซที่กำหนดมาอย่างดีและเข้าใจได้ง่ายจะช่วยให้เข้าใจ เขียน และทดสอบได้ง่ายขึ้น วิธีนี้ทำให้มีความน่าเชื่อถือมากขึ้น นอกจากนี้ยังทำให้โค้ดปลอดภัยและเสถียรยิ่งขึ้น ลดข้อขัดข้องและข้อบกพร่องในการใช้งานหลังจากที่ใช้งานฟรีแล้ว
  • ขยายความครอบคลุมของการทดสอบ: ใน BlinkNG เราได้เพิ่มการทดสอบใหม่ๆ ที่ยอดเยี่ยมในชุดโปรแกรมของเรา ซึ่งรวมถึงการทดสอบ 1 หน่วยที่ช่วยให้มีการยืนยันภายในโดยเฉพาะ การทดสอบการถดถอยที่ป้องกันไม่ให้เรานำข้อบกพร่องเก่าๆ ที่เราได้แก้ไขไปแล้วกลับมาอีกครั้ง (มีจำนวนมาก) และส่วนเพิ่มเติมอีกมากมายแก่สาธารณะ ที่ดูแลรักษารวมกันโดยชุดทดสอบแพลตฟอร์มเว็บ ซึ่งเบราว์เซอร์ทั้งหมดใช้เพื่อวัดความสอดคล้องกับมาตรฐานเว็บ
  • ขยายการใช้งานได้ง่ายกว่า: หากระบบแบ่งออกเป็นองค์ประกอบต่างๆ ที่ชัดเจน ก็ไม่จำเป็นต้องเข้าใจองค์ประกอบอื่นๆ อย่างละเอียดในระดับหนึ่งเพื่อทำงานกับองค์ประกอบปัจจุบัน วิธีนี้ทำให้ทุกคนเพิ่มมูลค่าให้กับโค้ดการแสดงผลได้ง่ายขึ้นโดยไม่ต้องเป็นผู้เชี่ยวชาญขั้นสูง และยังทำให้อธิบายลักษณะการทำงานของทั้งระบบได้ง่ายขึ้นด้วย
  • ประสิทธิภาพ: การเพิ่มประสิทธิภาพอัลกอริทึมที่เขียนด้วยโค้ดสปาเกตตีนั้นเป็นเรื่องที่ยากพอ แต่แทบจะเป็นไปไม่ได้เลยที่จะบรรลุเป้าหมายที่ยิ่งใหญ่กว่านั้น เช่น การเลื่อนและภาพเคลื่อนไหวแบบเทรดสากล หรือกระบวนการและชุดข้อความสำหรับการแยกเว็บไซต์โดยไม่ต้องมีไปป์ไลน์ การทำงานพร้อมกันจะช่วยให้เราปรับปรุงประสิทธิภาพได้อย่างมาก แต่ก็มีความซับซ้อนมากเช่นกัน
  • การตอบสนองได้และการกักกัน: BlinkNG มีฟีเจอร์ใหม่ๆ มากมายที่สามารถทำได้โดยใช้ไปป์ไลน์ในรูปแบบใหม่และแปลกใหม่ ตัวอย่างเช่น หากเราต้องการเรียกใช้เพียงไปป์ไลน์การแสดงผลจนกว่างบประมาณจะหมดอายุ หรือข้ามการแสดงผลสำหรับแผนผังย่อยที่ทราบว่าไม่เกี่ยวข้องกับผู้ใช้ในขณะนี้ ซึ่งเป็นสิ่งที่พร็อพเพอร์ตี้ CSS การเปิดเผยเนื้อหาจะเปิดใช้ หรือจะสร้างรูปแบบของคอมโพเนนต์ขึ้นอยู่กับเลย์เอาต์อย่างไร นี่ก็คือคำค้นหาคอนเทนเนอร์

กรณีศึกษา: การค้นหาคอนเทนเนอร์

การค้นหาคอนเทนเนอร์เป็นฟีเจอร์ของแพลตฟอร์มบนเว็บที่กำลังจะเปิดตัวในเร็วๆ นี้ (ซึ่งเป็นฟีเจอร์ที่มีการร้องขอมากที่สุดจากนักพัฒนาซอฟต์แวร์ CSS มาเป็นเวลาหลายปี) หากดีเยี่ยมขนาดนั้น ทำไมจึงยังไม่มี เหตุผลก็คือการใช้งานคำค้นหาคอนเทนเนอร์ต้องอาศัยความเข้าใจและการควบคุมความสัมพันธ์ระหว่างรูปแบบกับโค้ดเลย์เอาต์อย่างระมัดระวัง มาดูรายละเอียดกันเลย

การค้นหาคอนเทนเนอร์ช่วยให้รูปแบบที่ใช้กับองค์ประกอบขึ้นอยู่กับขนาดที่วางระดับบน เนื่องจากมีการคำนวณขนาดที่จัดวางระหว่างการออกแบบ นั่นหมายความว่าเราจำเป็นต้องเรียกใช้การคำนวณรูปแบบใหม่หลังจากเลย์เอาต์ แต่ Style Calculator ทำงานก่อนเลย์เอาต์ ความขัดแย้งระหว่างไก่กับไข่นี้คือเหตุผลทั้งหมดที่ทำให้เราไม่สามารถใช้การค้นหาคอนเทนเนอร์ก่อน BlinkNG ได้

เราจะแก้ปัญหานี้ได้อย่างไร ปัญหานี้เป็นทรัพยากร Dependency แบบย้อนหลังใช่ไหม กล่าวคือเป็นปัญหาเดียวกับที่โปรเจ็กต์อย่าง Composite หลังจาก Paint ได้รับการแก้ไขแล้วใช่ไหม ยิ่งไปกว่านั้น หากรูปแบบใหม่ทำให้ขนาดระดับบนเปลี่ยนไป เหตุการณ์นี้จะนำไปสู่การวนซ้ำที่ไม่สิ้นสุดใช่หรือไม่

โดยหลักการแล้ว การขึ้นต่อกันแบบเวียนกลับสามารถแก้ไขได้โดยใช้พร็อพเพอร์ตี้ CSS ที่ทำให้แสดงผลนอกองค์ประกอบที่ไม่ต้องอาศัยการแสดงผลภายในแผนผังย่อยขององค์ประกอบนั้น ซึ่งหมายความว่ารูปแบบใหม่ที่คอนเทนเนอร์ใช้จะไม่มีผลต่อขนาดของคอนเทนเนอร์ เนื่องจากคำค้นหาคอนเทนเนอร์จำเป็นต้องมี

แต่จริงๆ แล้วมันก็ไม่เพียงพอ เราจึงจำเป็นต้องใช้ประเภทการกักเก็บที่หละหลวมกว่าการกักเก็บขนาดเท่านั้น นั่นเป็นเพราะการที่คอนเทนเนอร์การค้นหาคอนเทนเนอร์สามารถปรับขนาดได้ในทิศทางเดียวเท่านั้น (โดยทั่วไปจะบล็อก) ตามมิติข้อมูลในบรรทัดของคอนเทนเนอร์นั้นเป็นเรื่องปกติ ดังนั้นจึงมีการเพิ่มแนวคิดการกักเก็บขนาดในบรรทัด แต่อย่างที่เห็นจากบันทึกที่ยาวมากในส่วนนั้น ไม่ชัดเจนเป็นเวลานานเลยถ้าสามารถจำกัดขนาดที่แทรกอยู่ในบรรทัดได้

การอธิบายการกักเก็บในข้อมูลจำเพาะเชิงนามธรรมเป็นเรื่องหนึ่งของการนำมาใช้อย่างถูกต้อง อย่าลืมว่าเป้าหมายอย่างหนึ่งของ BlinkNG คือการนำหลักการยับยั้งไปใช้กับการเดินบนต้นไม้ซึ่งประกอบขึ้นเป็นตรรกะหลักของการแสดงภาพ กล่าวคือเมื่อข้ามผ่านต้นไม้ย่อย ก็ไม่ควรมีข้อมูลจากภายนอกซับทรี ซึ่งก็เพราะ (ก็ไม่ได้บังเอิญเสมอไป) ดูสะอาดตาขึ้นมากและติดตั้งใช้งานการควบคุม CSS ได้ง่ายขึ้นหากโค้ดการแสดงผลเป็นไปตามหลักการยับยั้ง

อนาคต: การประกอบชุดข้อความหลัก ... และต่อไปในอนาคต

ไปป์ไลน์การแสดงผลที่แสดงที่นี่จะเร็วกว่าการติดตั้งใช้งาน RenderingNG ในปัจจุบันเล็กน้อย โดยจะแสดงการสร้างเลเยอร์ว่าอยู่นอกเทรดหลัก ขณะที่ในปัจจุบัน เลเยอร์ดังกล่าวยังคงอยู่ในเทรดหลัก แต่หลังจากที่ Composite After Paint ได้รับการจัดส่งแล้ว และการจำแนกเลเยอร์เกิดขึ้นหลังการลงสี

เราต้องพิจารณาสถาปัตยกรรมของเครื่องมือแสดงภาพจากจุดที่มองเห็นพื้นที่ได้อย่างกว้างขวางมากขึ้นเพื่อให้เข้าใจว่าเหตุใดขั้นตอนนี้จึงสำคัญ หนึ่งในอุปสรรคที่มั่นคงที่สุดสำหรับการปรับปรุงประสิทธิภาพของ Chromium คือข้อเท็จจริงง่ายๆ ที่เทรดหลักของตัวแสดงผลจัดการทั้งตรรกะของแอปพลิเคชันหลัก (ซึ่งก็คือการเรียกใช้สคริปต์) และการแสดงผลจำนวนมาก เป็นผลให้เทรดหลักมักจะเต็มอิ่มไปกับงาน และความคับคั่งของเทรดหลักมักทำให้เกิดจุดคอขวดของทั้งเบราว์เซอร์

ข่าวดีก็คือไม่จำเป็นต้องเป็นแบบนี้ สถาปัตยกรรมของ Chromium ในด้านนี้มีข้อมูลมาตั้งแต่ช่วง KHTML เมื่อการดำเนินการแบบเทรดเดี่ยวเป็นโมเดลการเขียนโปรแกรมที่โดดเด่น เมื่อระบบประมวลผลแบบโปรเซสเซอร์แบบมัลติคอร์ในปัจจุบันในอุปกรณ์ระดับผู้บริโภค ก็จะถือว่าระบบถือว่าระบบถือว่าตัวประมวลผลแบบเดี่ยวนี้รวมอยู่ใน Blink (ก่อนหน้านี้เรียกว่า WebKit) อย่างสมบูรณ์ เราต้องการเพิ่ม Threading เพิ่มเติมในเครื่องมือการแสดงภาพมานานแล้ว แต่เป็นไปไม่ได้เลยในระบบเก่า จุดประสงค์หลักของการแสดงภาพ NG อย่างหนึ่งคือเพื่อขจัดตัวเองให้หลุดพ้นจากช่องโหว่นี้และทำให้สามารถย้ายการแสดงภาพบางส่วนหรือทั้งหมดไปยังเธรด (หรือเธรดอื่น) ได้

และเนื่องจากตอนนี้ BlinkNG ใกล้บรรลุเป้าหมายแล้ว เราจึงเริ่มสำรวจพื้นที่นี้ การคอมมิตแบบไม่บล็อกเป็นวิธีการแรกในการเปลี่ยนรูปแบบการแยกชุดข้อความของโหมดแสดงภาพ คอมมิตคอมโพสิต (หรือแค่ commit) เป็นขั้นตอนการซิงค์ระหว่างเทรดหลักและเทรดคอมโพสิต ระหว่างคอมมิต เราจะทําสําเนาของข้อมูลการแสดงผลที่สร้างขึ้นในเทรดหลัก เพื่อใช้กับโค้ดการประกอบดาวน์สตรีมที่ทำงานอยู่บนเทรดคอมโพสิต ขณะที่การซิงค์นี้กำลังดำเนินอยู่ การดำเนินการเทรดหลักจะหยุดลงในขณะที่การคัดลอกโค้ดทำงานบนเทรดคอมโพสิต ซึ่งวิธีนี้เพื่อให้มั่นใจว่าเทรดหลักจะไม่แก้ไขข้อมูลการแสดงผลในขณะที่เทรดคอมโพสิตกำลังคัดลอกเทรด

การคอมมิตแบบไม่บล็อกจะทำให้เทรดหลักหยุดทำงานและรอให้ขั้นตอนการคอมมิตสิ้นสุดลง โดยเทรดหลักจะยังคงทำงานต่อไปในขณะที่คอมมิตทำงานพร้อมกันในเทรดคอมโพสิต ผลกระทบสุทธิของสัญญาผูกมัดแบบไม่บล็อกจะลดเวลาที่ใช้ในการแสดงผลงานในเทรดหลัก ซึ่งจะช่วยลดความแออัดในเทรดหลักและปรับปรุงประสิทธิภาพ ในขณะที่เขียนบทความนี้ (มีนาคม 2022) เรามีต้นแบบที่กำลังใช้งานอยู่แล้ว นั่นคือ การไม่บล็อก (Non-Block Commit) และเรากำลังเตรียมวิเคราะห์แบบละเอียดเกี่ยวกับผลกระทบที่มีต่อประสิทธิภาพ

การรอในปีกคือการจัดองค์ประกอบนอกชุดข้อความหลัก โดยมีเป้าหมายเพื่อทำให้เครื่องมือการแสดงผลตรงกับภาพประกอบโดยการย้ายการกำหนดเลเยอร์ออกจากเทรดหลัก และไปที่เทรดผู้ปฏิบัติงาน การดำเนินการนี้จะช่วยลดความคับคั่งในเทรดหลักได้ด้วยการลดปริมาณการแสดงผลในเทรดหลัก เช่นเดียวกับการคอมมิตแบบไม่บล็อก โครงการเช่นนี้คงเป็นไปไม่ได้เลยหากไม่มีการปรับปรุงเชิงสถาปัตยกรรมของ Composite After Paint

และยังมีโปรเจ็กต์อีกมากมายรอคุณอยู่ (พูดง่ายๆ ก็คือ) ในที่สุดเราก็มีพื้นฐานที่จะช่วยให้สามารถทดสอบการกระจายผลงานซ้ำ และเรายังตื่นเต้นมากที่ได้เห็นสิ่งที่เป็นไปได้