Blink หมายถึงการใช้งานแพลตฟอร์มเว็บของ Chromium และครอบคลุมทุกระยะของการเรนเดอร์ก่อนการคอมโพส ซึ่งจะสิ้นสุดที่คอมโพสิตคอมมิต อ่านข้อมูลเพิ่มเติมเกี่ยวกับสถาปัตยกรรมการแสดงผลของ Blink ได้ในบทความก่อนหน้าในชุดนี้
Blink เริ่มต้นจากการแยกมาจาก WebKit ซึ่งแยกมาจาก KHTML ตั้งแต่ปี 1998 โค้ดนี้เป็นหนึ่งในโค้ดที่เก่าแก่ที่สุด (และสำคัญที่สุด) ใน Chromium และในปี 2014 โค้ดนี้ก็เริ่มล้าสมัยแล้ว ในปีนั้น เราได้เริ่มโปรเจ็กต์ที่มีความทะเยอทะยานภายใต้แบนเนอร์ที่เราเรียกว่า BlinkNG โดยมีเป้าหมายเพื่อแก้ไขข้อบกพร่องที่มีมาอย่างยาวนานในองค์กรและโครงสร้างโค้ดของ Blink บทความนี้จะกล่าวถึง BlinkNG และโปรเจ็กต์ที่เกี่ยวข้อง ได้แก่ เหตุผลที่เราสร้างโปรเจ็กต์เหล่านี้ สิ่งที่โปรเจ็กต์เหล่านี้ทำสำเร็จ หลักการที่เป็นแนวทางซึ่งกำหนดการออกแบบ และโอกาสในการปรับปรุงในอนาคต
การแสดงผลก่อน NG
ไปป์ไลน์การแสดงผลภายใน Blink แบ่งออกเป็นระยะๆ (สไตล์ เลย์เอาต์ การวาด และอื่นๆ) เสมอ แต่การแยกแยะระดับนามธรรมนั้นไม่สมบูรณ์ กล่าวโดยคร่าวๆ ข้อมูลที่เกี่ยวข้องกับการแสดงผลประกอบด้วยออบเจ็กต์ที่มีการเปลี่ยนแปลงได้และมีอายุการใช้งานยาวนาน ออบเจ็กต์เหล่านี้สามารถแก้ไขได้ทุกเมื่อ และมีการแก้ไขอยู่บ่อยครั้ง รวมถึงมีการรีไซเคิลและนํากลับมาใช้ใหม่โดยการอัปเดตการแสดงผลอย่างต่อเนื่อง ไม่สามารถตอบคําถามง่ายๆ อย่างต่อไปนี้ได้อย่างน่าเชื่อถือ
- จำเป็นต้องอัปเดตเอาต์พุตของสไตล์ เลย์เอาต์ หรือสีไหม
- ข้อมูลเหล่านี้จะมีค่า "สุดท้าย" เมื่อใด
- ฉันจะแก้ไขข้อมูลเหล่านี้ได้เมื่อใด
- ระบบจะลบออบเจ็กต์นี้เมื่อใด
ตัวอย่างของเนื้อหาประเภทนี้ ได้แก่
สไตล์จะสร้าง ComputedStyle
ตามสไตล์ชีต แต่ ComputedStyle
นั้นเปลี่ยนแปลงได้ ในบางกรณี ComputedStyle
จะได้รับการปรับแต่งโดยขั้นตอนในไปป์ไลน์ที่ตามมา
สไตล์จะสร้างต้นไม้ของ LayoutObject
จากนั้นเลย์เอาต์จะกำกับเนื้อหาของออบเจ็กต์เหล่านั้นด้วยข้อมูลขนาดและตำแหน่ง ในบางกรณี เลย์เอาต์อาจแก้ไขโครงสร้างต้นไม้ด้วย ไม่มีการแยกอินพุตและเอาต์พุตของเลย์เอาต์อย่างชัดเจน
สไตล์จะสร้างโครงสร้างข้อมูลเสริมที่กำหนดเส้นทางของการคอมโพสิต และโครงสร้างข้อมูลเหล่านั้นจะได้รับการแก้ไขในแต่ละเฟสหลังจากสไตล์
ที่ระดับล่าง ประเภทข้อมูลการแสดงผลส่วนใหญ่ประกอบด้วยต้นไม้เฉพาะ (เช่น ต้นไม้ DOM, ต้นไม้สไตล์, ต้นไม้เลย์เอาต์, ต้นไม้พร็อพเพอร์ตี้การวาด) และระยะการแสดงผลจะใช้การเรียกใช้ต้นไม้แบบซ้ำ ตามหลักการแล้ว การตรวจสอบต้นไม้ควรจำกัด: เมื่อประมวลผลโหนดต้นไม้หนึ่งๆ เราไม่ควรเข้าถึงข้อมูลภายนอกซับต้นไม้ที่รูทอยู่ที่โหนดนั้น ซึ่งไม่เคยเป็นเช่นนั้นใน RenderingNG มาก่อน การเดินผ่านต้นไม้จะเข้าถึงข้อมูลจากบรรพบุรุษของโหนดที่ประมวลผลอยู่บ่อยครั้ง ซึ่งทำให้ระบบมีความเปราะบางและเกิดข้อผิดพลาดได้ง่าย นอกจากนี้ คุณยังเริ่มการเดินชมต้นไม้จากจุดอื่นที่ไม่ใช่รากของต้นไม้ไม่ได้
สุดท้าย ยังมีจุดเริ่มต้นจำนวนมากในไปป์ไลน์การแสดงผลที่กระจายอยู่ทั่วทั้งโค้ด เช่น เลย์เอาต์แบบบังคับที่ทริกเกอร์โดย JavaScript การอัปเดตบางส่วนที่ทริกเกอร์ระหว่างการโหลดเอกสาร การอัปเดตแบบบังคับเพื่อเตรียมพร้อมสำหรับการกําหนดเป้าหมายเหตุการณ์ การอัปเดตตามกำหนดการที่ระบบแสดงผลร้องขอ และ API เฉพาะที่แสดงเฉพาะกับโค้ดทดสอบ เป็นต้น ยังมีเส้นทางแบบเรียกซ้ำและแบบเข้าซ้ำไปยังไปป์ไลน์การแสดงผลอีก 2-3 เส้นทาง (กล่าวคือ การข้ามไปยังจุดเริ่มต้นของระยะหนึ่งจากช่วงกลางของอีกระยะหนึ่ง) แต่ละ On-ramp เหล่านี้มีลักษณะการทำงานเฉพาะตัว และผลลัพธ์ของการแสดงผลในบางกรณีจะขึ้นอยู่กับลักษณะที่เรียกให้เกิดการอัปเดตการแสดงผล
สิ่งที่เราเปลี่ยนแปลง
BlinkNG ประกอบด้วยโปรเจ็กต์ย่อยจำนวนมาก ทั้งขนาดใหญ่และขนาดเล็ก โดยมีเป้าหมายร่วมกันในการขจัดข้อบกพร่องด้านสถาปัตยกรรมที่อธิบายไว้ก่อนหน้านี้ โปรเจ็กต์เหล่านี้มีหลักการพื้นฐานบางอย่างที่ออกแบบมาเพื่อทำให้ไปป์ไลน์การแสดงผลเป็นไปป์ไลน์จริงมากขึ้น ดังนี้
- จุดเริ่มต้นแบบสอดคล้องกัน: เราควรเข้าสู่ไปป์ไลน์ตั้งแต่ต้นเสมอ
- ระยะการทำงาน: แต่ละระยะควรมีอินพุตและเอาต์พุตที่กําหนดไว้อย่างชัดเจน และลักษณะการทํางานควรทํางานได้ กล่าวคือ เป็นแบบกำหนดได้และทําซ้ำได้ และเอาต์พุตควรขึ้นอยู่กับอินพุตที่กําหนดไว้เท่านั้น
- อินพุตคงที่: อินพุตของระยะใดระยะหนึ่งควรคงที่ในขณะที่ระยะนั้นทํางาน
- เอาต์พุตแบบคงที่: เมื่อขั้นตอนหนึ่งเสร็จสิ้นแล้ว เอาต์พุตของขั้นตอนนั้นควรจะคงที่ตลอดการอัปเดตการแสดงผลที่เหลือ
- ความสอดคล้องของจุดตรวจสอบ: เมื่อสิ้นสุดแต่ละระยะ ข้อมูลการแสดงผลที่สร้างขึ้นจนถึงตอนนี้ควรอยู่ในสถานะที่สอดคล้องกัน
- การกรองข้อมูลงานซ้ำออก: ประมวลผลแต่ละรายการเพียงครั้งเดียว
รายการโปรเจ็กต์ย่อยทั้งหมดของ BlinkNG นั้นอ่านยาก แต่ต่อไปนี้คือผลลัพธ์บางส่วน
วงจรของเอกสาร
คลาส DocumentLifecycle จะติดตามความคืบหน้าของเราผ่านไปป์ไลน์การแสดงผล ซึ่งช่วยให้เราทำการตรวจสอบขั้นพื้นฐานที่บังคับใช้ค่าคงที่ที่ระบุไว้ก่อนหน้านี้ได้ เช่น
- หากเราแก้ไขพร็อพเพอร์ตี้ ComputedStyle วงจรชีวิตของเอกสารต้องเป็น
kInStyleRecalc
- หากสถานะ DocumentLifecycle เป็น
kStyleClean
ขึ้นไปNeedsStyleRecalc()
ต้องแสดงผลเป็น false สำหรับโหนดที่แนบมา - เมื่อเข้าสู่ระยะวงจร paint สถานะวงจรต้องเท่ากับ
kPrePaintClean
ตลอดระยะเวลาการใช้งาน BlinkNG เราได้กำจัดเส้นทางโค้ดที่ละเมิดค่าคงที่เหล่านี้อย่างเป็นระบบ และใส่การยืนยันอื่นๆ อีกมากมายไว้ในโค้ดเพื่อให้มั่นใจว่าจะไม่เกิดข้อบกพร่องซ้ำ
หากเคยลองดูโค้ดการแสดงผลระดับล่าง คุณอาจถามตัวเองว่า "ฉันมาอยู่ที่นี่ได้อย่างไร" ดังที่ได้กล่าวไปก่อนหน้านี้ มีหลายจุดที่เข้าสู่ไปป์ไลน์การแสดงผล ก่อนหน้านี้ ข้อมูลนี้รวมถึงเส้นทางการเรียกแบบซ้ำและแบบเข้าซ้ำ และตำแหน่งที่เราเข้าสู่ไปป์ไลน์ในระยะกลางแทนที่จะเริ่มต้นจากต้น ในระหว่างที่ใช้ BlinkNG เราวิเคราะห์เส้นทางการเรียกเหล่านี้และพบว่าเส้นทางทั้งหมดสามารถแบ่งออกเป็น 2 สถานการณ์พื้นฐาน ดังนี้
- ข้อมูลการแสดงผลทั้งหมดต้องได้รับการอัปเดต เช่น เมื่อสร้างพิกเซลใหม่สําหรับการแสดงผลหรือทำการทดสอบ Hit สำหรับการกำหนดเป้าหมายเหตุการณ์
- เราต้องการค่าที่เป็นปัจจุบันสำหรับคำค้นหาที่เฉพาะเจาะจงซึ่งตอบได้โดยไม่ต้องอัปเดตข้อมูลการแสดงผลทั้งหมด ซึ่งรวมถึงการค้นหา JavaScript ส่วนใหญ่ เช่น
node.offsetTop
ตอนนี้มีจุดแรกเข้าในไปป์ไลน์การแสดงผลเพียง 2 จุด ซึ่งสอดคล้องกับ 2 สถานการณ์นี้ เราได้นําเส้นทางโค้ดที่กลับมาใช้ใหม่ออกหรือปรับโครงสร้างแล้ว และคุณจะเข้าสู่ไปป์ไลน์ที่เริ่มต้นในระยะกลางไม่ได้อีกต่อไป วิธีนี้ช่วยไขปริศนาเกี่ยวกับเวลาและวิธีอัปเดตการแสดงผลที่แม่นยำ ซึ่งทำให้อธิบายลักษณะการทํางานของระบบได้ง่ายขึ้นมาก
รูปแบบ เลย์เอาต์ และการพ่นสีก่อน
โดยรวมแล้ว ระยะการแสดงผลก่อนPaint จะมีหน้าที่ดังต่อไปนี้
- เรียกใช้อัลกอริทึม style cascade เพื่อคํานวณพร็อพเพอร์ตี้สไตล์สุดท้ายสําหรับโหนด DOM
- การสร้างต้นไม้เลย์เอาต์ที่แสดงลําดับชั้นของกล่องในเอกสาร
- กำหนดข้อมูลขนาดและตำแหน่งของกล่องทั้งหมด
- การปัดเศษหรือจัดตำแหน่งเรขาคณิตระดับพิกเซลย่อยให้อยู่ตรงขอบพิกเซลทั้งพิกเซลสำหรับการวาด
- การกำหนดคุณสมบัติของเลเยอร์คอมโพสิต (การเปลี่ยนรูปแบบเชิงเรขาคณิต ตัวกรอง ความทึบ หรือสิ่งอื่นๆ ที่เร่งด้วย GPU ได้)
- การพิจารณาว่าเนื้อหาใดมีการเปลี่ยนแปลงนับตั้งแต่ช่วงการแสดงผลภาพก่อนหน้า และจำเป็นต้องมีการแสดงผลภาพหรือแสดงผลภาพอีกครั้ง (การทำให้การแสดงผลภาพใช้งานไม่ได้)
รายการนี้ไม่มีการเปลี่ยนแปลง แต่ก่อน BlinkNG งานส่วนใหญ่นี้ทำแบบเฉพาะกิจ กระจายไปทั่วทั้งขั้นตอนการเรนเดอร์หลายขั้นตอน โดยมีฟังก์ชันการทำงานที่ซ้ำกันจำนวนมากและทำงานอย่างไม่มีประสิทธิภาพ ตัวอย่างเช่น ระยะสไตล์มีหน้าที่หลักในการคำนวณพร็อพเพอร์ตี้สไตล์สุดท้ายสำหรับโหนดมาโดยตลอด แต่ก็มีบางกรณีที่พิเศษซึ่งเราไม่ได้กำหนดค่าพร็อพเพอร์ตี้สไตล์สุดท้ายจนกว่าระยะสไตล์จะเสร็จสมบูรณ์ ไม่มีจุดอย่างเป็นทางการหรือบังคับใช้ในกระบวนการแสดงผลที่เราสามารถพูดได้อย่างมั่นใจว่าข้อมูลสไตล์นั้นสมบูรณ์และเปลี่ยนแปลงไม่ได้
อีกตัวอย่างที่ดีของปัญหาก่อน BlinkNG คือ การทำให้การวาดภาพใช้งานไม่ได้ ก่อนหน้านี้ การลบล้างการวาดภาพจะกระจายอยู่ตลอดระยะการแสดงผลทั้งหมดที่นำไปสู่การวาดภาพ เมื่อแก้ไขโค้ดสไตล์หรือเลย์เอาต์ ผู้ใช้จะไม่ทราบถึงการเปลี่ยนแปลงที่จำเป็นในตรรกะการลบล้างการวาด และอาจทําผิดพลาดได้ง่าย ซึ่งทําให้เกิดข้อบกพร่องการลบล้างมากหรือน้อยเกินไป อ่านข้อมูลเพิ่มเติมเกี่ยวกับความซับซ้อนของระบบการทำให้ภาพวาดใช้งานไม่ได้แบบเก่าได้ในบทความจากชุดบทความที่เน้นเรื่อง LayoutNG
การจับคู่เรขาคณิตของเลย์เอาต์พิกเซลย่อยกับขอบเขตพิกเซลทั้งภาพสำหรับการวาดภาพเป็นตัวอย่างที่เรามีการใช้งานฟังก์ชันการทำงานเดียวกันหลายครั้งและทํางานซ้ำซ้อนกันมาก ระบบการวาดใช้เส้นทางโค้ดการจับคู่พิกเซล 1 เส้นทาง และเส้นทางโค้ดแยกต่างหากทั้งหมดที่ใช้เมื่อใดก็ตามที่เราต้องการการคำนวณพิกัดการจับคู่พิกเซลแบบครั้งเดียวขณะดำเนินการนอกโค้ดการวาด ไม่ต้องบอกก็รู้ว่าการติดตั้งใช้งานแต่ละครั้งมีข้อบกพร่องเป็นของตัวเอง และผลลัพธ์ที่ได้อาจไม่ตรงกันเสมอไป เนื่องจากไม่มีการแคชข้อมูลนี้ ในบางครั้งระบบจึงทำการคํานวณแบบเดียวกันซ้ำๆ ซึ่งส่งผลเสียต่อประสิทธิภาพ
ต่อไปนี้คือโปรเจ็กต์สำคัญบางโปรเจ็กต์ที่ช่วยลดข้อบกพร่องทางสถาปัตยกรรมของระยะการแสดงผลก่อนการทาสี
Project Squad: การจัดลำดับขั้นตอนสำหรับช่วงสไตล์
โปรเจ็กต์นี้จัดการกับข้อบกพร่องหลัก 2 ข้อในระยะสไตล์ ซึ่งทำให้วางไปป์ไลน์ได้อย่างราบรื่น
ผลลัพธ์หลักของระยะการจัดสไตล์มี 2 อย่าง ได้แก่ ComputedStyle
ซึ่งมีผลการเรียกใช้อัลกอริทึม CSS Cascade บนต้นไม้ DOM และต้นไม้ LayoutObjects
ซึ่งกำหนดลําดับการดําเนินการของระยะเลย์เอาต์ ในทางแนวคิด การทำงานของอัลกอริทึมการแสดงผลตามลำดับชั้นควรเกิดขึ้นก่อนการสร้างต้นไม้เลย์เอาต์อย่างเคร่งครัด แต่ก่อนหน้านี้ การดำเนินการ 2 รายการนี้ทำงานสลับกัน Project Squad แยก 2 รายการนี้ออกเป็น 2 ระยะที่แยกกัน
ก่อนหน้านี้ ComputedStyle
ไม่ได้ค่าสุดท้ายเสมอไปในระหว่างการคํานวณสไตล์ใหม่ มีหลายกรณีที่ ComputedStyle
ได้รับการอัปเดตในเฟสไปป์ไลน์ในภายหลัง Project Squad ปรับโครงสร้างเส้นทางโค้ดเหล่านี้เรียบร้อยแล้ว เพื่อไม่ให้ ComputedStyle
ได้รับการแก้ไขหลังจากระยะรูปแบบ
LayoutNG: การจัดเรียงขั้นตอนเลย์เอาต์
โปรเจ็กต์ที่ยิ่งใหญ่นี้ ซึ่งเป็นหนึ่งในรากฐานสำคัญของ RenderingNG เป็นการเขียนใหม่ทั้งหมดของขั้นตอนการแสดงผลเลย์เอาต์ เราจะไม่พูดถึงทั้งโปรเจ็กต์ที่นี่ แต่มีประเด็นที่น่าสนใจ 2-3 ข้อสำหรับโปรเจ็กต์ BlinkNG โดยรวม ดังนี้
- ก่อนหน้านี้ ระยะการวางเลย์เอาต์จะได้รับต้นไม้ของ
LayoutObject
ที่สร้างขึ้นโดยระยะสไตล์ และกำกับเนื้อหาของต้นไม้ด้วยข้อมูลขนาดและตำแหน่ง จึงไม่มีการแยกอินพุตออกจากเอาต์พุตอย่างชัดเจน LayoutNG ได้เปิดตัวต้นไม้เศษส่วน ซึ่งเป็นเอาต์พุตหลักที่อ่านอย่างเดียวของเลย์เอาต์ และใช้เป็นอินพุตหลักสำหรับระยะการแสดงผลต่อๆ ไป - LayoutNG นำพร็อพเพอร์ตี้การบรรจุมาใช้กับเลย์เอาต์: เมื่อคำนวณขนาดและตำแหน่งของ
LayoutObject
หนึ่งๆ เราจะไม่มองหานอกซับต้นไม้ที่รูทอยู่ที่ออบเจ็กต์นั้นอีกต่อไป ระบบจะคํานวณข้อมูลทั้งหมดที่จําเป็นสําหรับการอัปเดตเลย์เอาต์สําหรับออบเจ็กต์หนึ่งๆ ล่วงหน้าและส่งเป็นอินพุตแบบอ่านอย่างเดียวไปยังอัลกอริทึม - ก่อนหน้านี้ อัลกอริทึมการจัดวางอาจไม่ทำงานอย่างเต็มประสิทธิภาพในบางกรณี โดยผลลัพธ์ของอัลกอริทึมจะขึ้นอยู่กับการอัปเดตเลย์เอาต์ล่าสุดก่อนหน้านี้ LayoutNG จะช่วยแก้ปัญหาเหล่านั้น
ระยะก่อนแสดงผล
ก่อนหน้านี้ไม่มีขั้นตอนการเรนเดอร์ก่อนการวาดภาพอย่างเป็นทางการ เป็นเพียงการดำเนินการหลังการจัดวางแบบผสมผสาน ระยะการวาดภาพล่วงหน้าเกิดขึ้นจากการตระหนักว่ายังมีฟังก์ชันที่เกี่ยวข้องอีก 2-3 รายการที่ควรนำมาใช้เป็นการเรียกใช้อย่างเป็นระบบของต้นไม้เลย์เอาต์หลังจากที่เลย์เอาต์เสร็จสมบูรณ์แล้ว ที่สำคัญที่สุดคือ
- การออกการลบล้างการทาสี: การลบล้างการทาสีอย่างถูกต้องในระหว่างขั้นตอนการจัดวางนั้นทำได้ยากมากเมื่อเรามีข้อมูลไม่ครบถ้วน การทำเช่นนี้จะทำให้คุณทำสิ่งต่างๆ ได้อย่างถูกต้องและมีประสิทธิภาพมากขึ้นมาก หากแยกออกเป็น 2 กระบวนการที่แตกต่างกัน โดยในระหว่างการจัดสไตล์และเลย์เอาต์ คุณสามารถทําเครื่องหมายเนื้อหาด้วย Flag แบบบูลีนง่ายๆ ว่า "อาจต้องลบล้างการวาด" ในระหว่างการเดินผ่านต้นไม้ก่อนทาสี เราจะตรวจสอบการแจ้งว่าไม่เหมาะสมเหล่านี้และออกการลบล้างตามความจําเป็น
- การสร้างต้นไม้พร็อพเพอร์ตี้สี: กระบวนการที่อธิบายไว้อย่างละเอียดในลำดับถัดไป
- การคํานวณและบันทึกตําแหน่งการวาดที่จับคู่พิกเซล: เฟสการวาดและโค้ดดาวน์สตรีมที่ต้องการใช้ผลลัพธ์ที่บันทึกไว้ได้โดยไม่ต้องคํานวณซ้ำ
ต้นไม้พร็อพเพอร์ตี้: เรขาคณิตที่สอดคล้องกัน
ต้นไม้พร็อพเพอร์ตี้เปิดตัวในช่วงต้นของ RenderingNG เพื่อจัดการกับความซับซ้อนของการเลื่อน ซึ่งในเว็บจะมีโครงสร้างแตกต่างจากเอฟเฟกต์ภาพประเภทอื่นๆ ทั้งหมด ก่อนที่จะมีต้นไม้พร็อพเพอร์ตี้ คอมโพสิตอร์ของ Chromium ใช้ลําดับชั้น "เลเยอร์" เดียวเพื่อแสดงความสัมพันธ์เชิงเรขาคณิตของเนื้อหาที่คอมโพส แต่วิธีนี้ใช้ไม่ได้ผลอย่างรวดเร็วเมื่อฟีเจอร์ต่างๆ เช่น position:fixed มีความซับซ้อนมากขึ้น ลําดับชั้นของเลเยอร์มีตัวชี้ที่ไม่ใช่ภายในเพิ่มเติมซึ่งระบุ "องค์ประกอบหลักของการเลื่อน" หรือ "องค์ประกอบหลักของคลิป" ของเลเยอร์ และไม่นานนัก โค้ดก็เข้าใจได้ยากมาก
แผนภูมิพร็อพเพอร์ตี้แก้ไขปัญหานี้ด้วยการนำเสนอการเลื่อนแบบแสดงผลเกินและการครอบตัดเนื้อหาแยกจากเอฟเฟกต์ภาพอื่นๆ ทั้งหมด วิธีนี้ช่วยให้สามารถจำลองโครงสร้างภาพและโครงสร้างการเลื่อนที่แท้จริงของเว็บไซต์ได้อย่างถูกต้อง ขั้นตอนถัดไป "ทั้งหมด" ที่เราต้องทำคือใช้อัลกอริทึมบนต้นไม้พร็อพเพอร์ตี้ เช่น การเปลี่ยนรูปแบบพื้นที่หน้าจอของเลเยอร์แบบคอมโพสิต หรือการกำหนดเลเยอร์ที่จะเลื่อนและเลเยอร์ที่จะไม่เลื่อน
ไม่นานหลังจากนั้น เราสังเกตเห็นจุดอื่นๆ อีกหลายจุดในโค้ดที่ถามคำถามเกี่ยวกับเรขาคณิตที่คล้ายกัน (โพสต์เกี่ยวกับโครงสร้างข้อมูลที่สำคัญมีรายการที่สมบูรณ์ยิ่งขึ้น) หลายรายการมีการใช้งานซ้ำกันซึ่งทําสิ่งเดียวกันกับที่โค้ดคอมโพสิเตอร์ทํา ทั้งหมดมีข้อบกพร่องชุดย่อยที่แตกต่างกัน และไม่มีรายการใดจำลองโครงสร้างเว็บไซต์จริงอย่างถูกต้อง จากนั้นเราก็พบวิธีแก้ปัญหาที่ชัดเจนแล้ว ซึ่งก็คือรวบรวมอัลกอริทึมเรขาคณิตทั้งหมดไว้ในที่เดียว และรีแฟกทอเรียลโค้ดทั้งหมดเพื่อใช้งาน
อัลกอริทึมเหล่านี้ล้วนแล้วแต่ขึ้นอยู่กับต้นไม้พร็อพเพอร์ตี้ ซึ่งเป็นเหตุผลที่ต้นไม้พร็อพเพอร์ตี้เป็นโครงสร้างข้อมูลสําคัญของ RenderingNG ซึ่งใช้ในไปป์ไลน์ทั้งหมด ดังนั้นเพื่อให้บรรลุเป้าหมายของโค้ดเรขาคณิตแบบรวมศูนย์นี้ เราจึงต้องแนะนำแนวคิดของต้นไม้พร็อพเพอร์ตี้ในไปป์ไลน์ตั้งแต่เนิ่นๆ นั่นคือในการแสดงผลก่อนวาด และเปลี่ยน API ทั้งหมดที่ตอนนี้ต้องอาศัยต้นไม้พร็อพเพอร์ตี้ให้ต้องเรียกใช้การแสดงผลก่อนวาดก่อนจึงจะทำงานได้
เรื่องราวนี้เป็นอีกแง่มุมหนึ่งของรูปแบบการรีแฟกทอริง BlinkNG ซึ่งได้แก่ ระบุการคํานวณที่สําคัญ รีแฟกทอริงเพื่อหลีกเลี่ยงการคํานวณซ้ำ และสร้างระยะของไปป์ไลน์ที่กําหนดไว้อย่างชัดเจน ซึ่งจะสร้างโครงสร้างข้อมูลที่ป้อนข้อมูล เราจะคํานวณต้นไม้พร็อพเพอร์ตี้ ณ จุดที่มีข้อมูลจําเป็นทั้งหมด และตรวจสอบว่าต้นไม้พร็อพเพอร์ตี้จะไม่เปลี่ยนแปลงขณะที่ขั้นตอนการแสดงผลในภายหลังทํางานอยู่
ภาพรวมหลังจากลงสี: การวางขั้นตอนการลงสีและการคอมโพส
การจัดเลเยอร์คือกระบวนการพิจารณาว่าเนื้อหา DOM รายการใดจะไปอยู่ในเลเยอร์คอมโพสิตของตัวเอง (ซึ่งจะแสดงพื้นผิวของ GPU) ก่อน RenderingNG การจัดเลเยอร์จะทำงานก่อนการทาสี ไม่ใช่หลังจากนั้น (ดูไปป์ไลน์ปัจจุบันได้ที่นี่ โปรดสังเกตการเปลี่ยนแปลงลำดับ) ก่อนอื่นเราจะเลือกว่าส่วนใดของ DOM จะไปอยู่ในเลเยอร์คอมโพสิตใด แล้วจึงวาดรายการแสดงผลสำหรับพื้นผิวเหล่านั้น แน่นอนว่าการตัดสินใจนี้ขึ้นอยู่กับปัจจัยต่างๆ เช่น องค์ประกอบ DOM ใดที่เคลื่อนไหวหรือเลื่อนอยู่ หรือมีการเปลี่ยนรูปแบบ 3 มิติ และองค์ประกอบใดที่วาดทับองค์ประกอบใด
ซึ่งทำให้เกิดปัญหาใหญ่ เนื่องจากจำเป็นต้องมีการพึ่งพาแบบวนซ้ำในโค้ด ซึ่งถือเป็นปัญหาใหญ่สำหรับไปป์ไลน์การแสดงผล มาดูเหตุผลกันผ่านตัวอย่าง สมมติว่าเราต้องทำให้การวาดภาพเป็นโมฆะ (หมายความว่าเราต้องวาดรายการการแสดงผลอีกครั้งแล้วแรสเตอร์อีกครั้ง) ความจำเป็นในการทำให้โมเดลไม่ถูกต้องอาจมาจากการเปลี่ยนแปลงใน DOM หรือจากสไตล์หรือเลย์เอาต์ที่เปลี่ยนแปลง แต่แน่นอนว่าเราต้องการทำให้เฉพาะส่วนที่เปลี่ยนแปลงจริงเป็นโมฆะ ซึ่งหมายความว่าต้องค้นหาเลเยอร์คอมโพสิตที่ได้รับผลกระทบ จากนั้นทำให้รายการที่แสดงของเลเยอร์เหล่านั้นบางส่วนหรือทั้งหมดเป็นโมฆะ
ซึ่งหมายความว่าการทำให้โมฆะจะขึ้นอยู่กับ DOM, สไตล์, เลย์เอาต์ และการตัดสินใจเกี่ยวกับเลเยอร์ที่ผ่านมา (ที่ผ่านมา: หมายถึงเฟรมที่ผ่านการแสดงผลแล้วก่อนหน้านี้) แต่การจัดเรียงเลเยอร์ในปัจจุบันก็ขึ้นอยู่กับปัจจัยทั้งหมดเหล่านั้นด้วย และเนื่องจากเราไม่มีข้อมูลเลเยอร์ทั้งหมด 2 ชุด จึงแยกความแตกต่างระหว่างการตัดสินใจเลเยอร์ในอดีตและอนาคตได้ยาก ด้วยเหตุนี้ เราจึงมีโค้ดจำนวนมากที่มีการใช้เหตุผลแบบวนซ้ำ ซึ่งบางครั้งอาจทำให้เกิดโค้ดที่ไม่ถูกต้องหรือไม่สมเหตุสมผล หรืออาจทำให้เกิดข้อขัดข้องหรือปัญหาด้านความปลอดภัยได้หากเราไม่ระมัดระวังมากพอ
ในช่วงแรกๆ เราได้แนะนำแนวคิดเกี่ยวกับออบเจ็กต์ DisableCompositingQueryAsserts
เพื่อจัดการกับสถานการณ์นี้ ในกรณีส่วนใหญ่ หากโค้ดพยายามค้นหาการตัดสินใจเกี่ยวกับการจัดวางเลเยอร์ที่ผ่านมา จะทำให้การยืนยันไม่สำเร็จและเบราว์เซอร์ขัดข้องหากอยู่ในโหมดแก้ไขข้อบกพร่อง ซึ่งช่วยให้เราหลีกเลี่ยงการเกิดข้อบกพร่องใหม่ๆ และในแต่ละกรณีที่โค้ดจําเป็นต้องค้นหาคําตัดสินการจัดเลเยอร์ที่ผ่านมาอย่างถูกต้องตามกฎหมาย เราจะใส่โค้ดเพื่ออนุญาตโดยการจัดสรรออบเจ็กต์ DisableCompositingQueryAsserts
แผนของเราคือค่อยๆ กำจัดออบเจ็กต์ DisableCompositingQueryAssert
ของเว็บไซต์การเรียกใช้ทั้งหมด แล้วประกาศว่าโค้ดปลอดภัยและถูกต้อง แต่สิ่งที่เราค้นพบคือคำเรียกจำนวนมากนั้นนำออกไม่ได้ ตราบใดที่การแบ่งเลเยอร์เกิดขึ้นก่อนการทาสี (เรานำวิดีโอออกได้เมื่อเร็วๆ นี้) นี่คือเหตุผลแรกที่พบสำหรับโปรเจ็กต์ภาพรวมหลังการทาสี สิ่งที่เราเรียนรู้คือ แม้ว่าคุณจะมีระยะของไปป์ไลน์ที่กําหนดไว้อย่างดีสําหรับการดำเนินการ แต่หากระยะดังกล่าวอยู่ในตําแหน่งที่ไม่ถูกต้องในไปป์ไลน์ คุณจะติดขัดในที่สุด
เหตุผลที่ 2 สำหรับโปรเจ็กต์คอมโพสหลังจากระบายสีคือข้อบกพร่องในการคอมโพสพื้นฐาน วิธีหนึ่งในการระบุข้อบกพร่องนี้คือ องค์ประกอบ DOM ไม่ใช่การนำเสนอ 1:1 ที่ดีสำหรับรูปแบบการจัดวางเลเยอร์ที่มีประสิทธิภาพหรือสมบูรณ์สำหรับเนื้อหาหน้าเว็บ และเนื่องจากการจัดวางภาพอยู่ก่อนการวาดภาพ การจัดวางภาพจึงอาศัยองค์ประกอบ DOM โดยพื้นฐานมากกว่า ไม่ได้อาศัยรายการที่แสดงหรือต้นไม้พร็อพเพอร์ตี้ เหตุผลนี้คล้ายกับเหตุผลที่เราเปิดตัวต้นไม้พร็อพเพอร์ตี้ และเช่นเดียวกับต้นไม้พร็อพเพอร์ตี้ โซลูชันจะปรากฏขึ้นโดยตรงหากคุณทราบระยะของไปป์ไลน์ที่เหมาะสม เรียกใช้ไปป์ไลน์ในเวลาที่เหมาะสม และให้โครงสร้างข้อมูลคีย์ที่ถูกต้อง และเช่นเดียวกับต้นไม้พร็อพเพอร์ตี้ นี่เป็นโอกาสที่ดีในการรับประกันว่าเมื่อช่วงการวาดภาพเสร็จสมบูรณ์แล้ว เอาต์พุตของช่วงดังกล่าวจะเปลี่ยนแปลงไม่ได้สำหรับช่วงไปป์ไลน์ที่ตามมาทั้งหมด
ประโยชน์
ดังที่คุณได้เห็นแล้วว่าไปป์ไลน์การแสดงผลที่กําหนดไว้อย่างดีจะให้ประโยชน์ระยะยาวมหาศาล ประโยชน์มีมากกว่าที่คุณคิด
- ความน่าเชื่อถือที่ดีขึ้นอย่างมาก: อันนี้ค่อนข้างตรงไปตรงมา โค้ดที่สะอาดตาขึ้นและมีอินเทอร์เฟซที่เข้าใจง่ายและชัดเจนจะช่วยให้เข้าใจ เขียน และทดสอบได้ง่ายขึ้น ซึ่งทำให้เชื่อถือได้มากขึ้น นอกจากนี้ ยังทําให้โค้ดปลอดภัยและเสถียรมากขึ้น มีการขัดข้องน้อยลง และข้อบกพร่องในการใช้หลังจากการปลดปล่อยน้อยลง
- การทดสอบที่ครอบคลุมมากขึ้น: ในระหว่างการพัฒนา BlinkNG เราได้เพิ่มการทดสอบใหม่จำนวนมากลงในชุดทดสอบ ซึ่งรวมถึงการทดสอบหน่วยที่ให้การตรวจสอบที่มุ่งเน้นที่ส่วนภายใน การทดสอบแบบย้อนกลับที่ช่วยป้องกันไม่ให้เราพบข้อบกพร่องเก่าๆ ที่เราแก้ไขไปแล้ว (มีเยอะมาก) และการเพิ่มจำนวนมากในชุดทดสอบแพลตฟอร์มเว็บแบบสาธารณะที่ดูแลรักษาร่วมกัน ซึ่งเบราว์เซอร์ทุกรุ่นใช้เพื่อวัดการปฏิบัติตามมาตรฐานเว็บ
- ขยายได้ง่ายขึ้น: หากระบบแบ่งออกเป็นคอมโพเนนต์ที่ชัดเจน คุณไม่จําเป็นต้องทําความเข้าใจคอมโพเนนต์อื่นๆ ในรายละเอียดระดับใดก็ตามเพื่อให้ระบบปัจจุบันทํางานได้ วิธีนี้ช่วยให้ทุกคนเพิ่มคุณค่าให้กับโค้ดการแสดงผลได้ง่ายขึ้นโดยไม่ต้องเป็นผู้เชี่ยวชาญด้านนี้มากนัก ทั้งยังช่วยให้สามารถหาเหตุผลเกี่ยวกับลักษณะการทํางานของระบบทั้งหมดได้ง่ายขึ้นด้วย
- ประสิทธิภาพ: การเพิ่มประสิทธิภาพอัลกอริทึมที่เขียนด้วยโค้ดสปาเก็ตตี้นั้นยากอยู่แล้ว แต่แทบจะเป็นไปไม่ได้เลยที่จะบรรลุเป้าหมายที่ยิ่งใหญ่ขึ้น เช่น การเลื่อนแบบเทรดและภาพเคลื่อนไหวแบบสากล หรือกระบวนการและเธรดสำหรับการแยกเว็บไซต์ หากไม่มีไปป์ไลน์ดังกล่าว การทำงานแบบขนานช่วยปรับปรุงประสิทธิภาพได้อย่างมาก แต่ก็ซับซ้อนมากเช่นกัน
- การลดจำนวนและการควบคุม: BlinkNG มีฟีเจอร์ใหม่ๆ หลายอย่างที่ทําให้ใช้ไปป์ไลน์ได้ในรูปแบบใหม่ๆ ตัวอย่างเช่น จะเกิดอะไรขึ้นหากเราต้องการเรียกใช้ไปป์ไลน์การแสดงผลจนกว่างบประมาณจะหมดอายุ หรือข้ามการแสดงผลสำหรับซับต้นไม้ที่ทราบว่าไม่เกี่ยวข้องกับผู้ใช้ในขณะนี้ พร็อพเพอร์ตี้ CSS content-visibility ช่วยให้ทำสิ่งนั้นได้ แล้วการทำให้สไตล์ของคอมโพเนนต์ขึ้นอยู่กับเลย์เอาต์ล่ะ นั่นคือคำค้นหาคอนเทนเนอร์
กรณีศึกษา: การค้นหาคอนเทนเนอร์
การค้นหาคอนเทนเนอร์เป็นฟีเจอร์แพลตฟอร์มเว็บที่กําลังจะเปิดตัวซึ่งทุกคนรอคอย (เป็นฟีเจอร์ที่นักพัฒนา CSS ขอมากที่สุดเป็นอันดับ 1 มาหลายปี) หากยอดเยี่ยมมาก ทำไมถึงยังไม่มี เนื่องจากการใช้งานการค้นหาคอนเทนเนอร์ต้องใช้ความเข้าใจและการควบคุมความสัมพันธ์ระหว่างโค้ดสไตล์กับเลย์เอาต์อย่างรอบคอบ มาดูรายละเอียดกันเลย
การค้นหาคอนเทนเนอร์ช่วยให้สไตล์ที่ใช้กับองค์ประกอบขึ้นอยู่กับขนาดที่วางไว้ของบรรพบุรุษ เนื่องจากระบบจะคํานวณขนาดที่วางไว้ระหว่างการจัดวาง เราจึงต้องเรียกใช้การคํานวณสไตล์อีกครั้งหลังการจัดวาง แต่การคํานวณสไตล์อีกครั้งจะทํางานก่อนการจัดวาง ความขัดแย้งนี้ถือเป็นเหตุผลทั้งหมดที่เราไม่สามารถใช้การค้นหาคอนเทนเนอร์ก่อน BlinkNG
เราจะแก้ปัญหานี้ได้อย่างไร ปัญหานี้ไม่ใช่การพึ่งพาไปป์ไลน์แบบย้อนหลัง ซึ่งเป็นปัญหาเดียวกับที่โปรเจ็กต์อย่าง Composite After Paint แก้ปัญหาได้ใช่ไหม ที่แย่กว่านั้นคือจะเกิดอะไรขึ้นหากรูปแบบใหม่เปลี่ยนขนาดของบรรพบุรุษ การดำเนินการนี้จะไม่ทำให้เกิดลูปที่ไม่มีที่สิ้นสุดในบางครั้งใช่ไหม
โดยหลักการแล้ว ปัญหาการพึ่งพาแบบวนซ้ำจะแก้ไขได้โดยใช้พร็อพเพอร์ตี้ CSS ของ contain ซึ่งช่วยให้การแสดงผลภายนอกองค์ประกอบไม่ต้องขึ้นอยู่กับการแสดงผลภายในซับต้นไม้ขององค์ประกอบนั้น ซึ่งหมายความว่ารูปแบบใหม่ที่ใช้โดยคอนเทนเนอร์จะไม่ส่งผลต่อขนาดของคอนเทนเนอร์ เนื่องจากการค้นหาคอนเทนเนอร์ต้องมีการบรรจุ
แต่จริงๆ แล้ววิธีนี้ยังไม่เพียงพอ และจำเป็นต้องมีการจำกัดประเภทที่อ่อนแอกว่าการจำกัดขนาด เนื่องจากโดยทั่วไปแล้ว คอนเทนเนอร์การค้นหาคอนเทนเนอร์ต้องการปรับขนาดได้ในทิศทางเดียวเท่านั้น (โดยปกติคือบล็อก) โดยอิงตามมิติข้อมูลในบรรทัด จึงมีการเพิ่มแนวคิดการจำกัดขนาดแบบอินไลน์ แต่อย่างที่คุณเห็นจากหมายเหตุที่ยาวมากในส่วนนั้น เป็นเวลานานมากที่เราไม่แน่ใจว่าการจำกัดขนาดในบรรทัดเป็นไปได้หรือไม่
การอธิบายการกักเก็บข้อมูลด้วยภาษาที่เป็นนามธรรมนั้นแตกต่างจากการใช้การกักเก็บข้อมูลอย่างถูกต้อง โปรดทราบว่าเป้าหมายหนึ่งของ BlinkNG คือการนำหลักการการจำกัดขอบเขตไปใช้กับการเดินผ่านต้นไม้ ซึ่งเป็นตรรกะหลักของการแสดงผล โดยเมื่อไปยังส่วนย่อยของต้นไม้ ก็ไม่ควรต้องใช้ข้อมูลจากภายนอกส่วนย่อยนั้น ด้วยเหตุนี้ (ซึ่งก็ไม่ใช่อุบัติเหตุที่เกิดขึ้นโดยบังเอิญ) การใช้การจำกัด CSS จึงง่ายกว่าและเรียบร้อยกว่ามากหากโค้ดการแสดงผลเป็นไปตามหลักการจำกัด
อนาคต: การคอมโพสนอกเธรดหลักและอื่นๆ
ไปป์ไลน์การแสดงผลที่แสดงที่นี่นั้นล้ำหน้ากว่าการใช้งาน RenderingNG ปัจจุบันเล็กน้อย ข้อมูลแสดงว่าเลเยอร์ไม่ได้อยู่ในชุดข้อความหลัก แต่ปัจจุบันยังอยู่ในชุดข้อความหลัก อย่างไรก็ตาม การดำเนินการนี้จะเสร็จสมบูรณ์ในไม่ช้า เนื่องจากตอนนี้ Composite After Paint ใช้งานได้แล้วและการจัดเลเยอร์จะอยู่หลังการทาสี
หากต้องการทําความเข้าใจว่าเหตุใดเรื่องนี้จึงสำคัญและอาจนำไปสู่อะไรอีกบ้าง เราจําเป็นต้องพิจารณาสถาปัตยกรรมของเครื่องมือแสดงผลจากมุมมองที่สูงขึ้น หนึ่งในอุปสรรคที่แก้ยากที่สุดในการปรับปรุงประสิทธิภาพของ Chromium คือข้อเท็จจริงง่ายๆ ที่ว่าเธรดหลักของโปรแกรมแสดงผลจะจัดการทั้งตรรกะหลักของแอปพลิเคชัน (นั่นคือสคริปต์ที่ทำงานอยู่) และการเรนเดอร์ส่วนใหญ่ ด้วยเหตุนี้ เทรดหลักจึงมีงานเข้ามามากจนทำงานไม่ทัน และบ่อยครั้งที่ความแออัดของเทรดหลักเป็นปัญหาคอขวดของทั้งเบราว์เซอร์
แต่ข่าวดีก็คือคุณไม่จำเป็นต้องทำแบบนั้น ลักษณะนี้ของสถาปัตยกรรม Chromium ย้อนกลับไปตั้งแต่สมัย KHTML เมื่อการประมวลผลแบบเธรดเดียวเป็นรูปแบบการเขียนโปรแกรมหลัก เมื่อโปรเซสเซอร์แบบหลายแกนกลายเป็นเรื่องปกติในอุปกรณ์ระดับผู้บริโภคแล้ว สมมติฐานแบบเธรดเดียวก็ฝังอยู่ใน Blink (เดิมคือ WebKit) อย่างสมบูรณ์ เราต้องการเพิ่มการแยกเธรดในเครื่องมือแสดงผลมานานแล้ว แต่ระบบเก่าไม่สามารถทำได้ หนึ่งในวัตถุประสงค์หลักของ Rendering NG คือการขุดหลุมนี้ให้หลุดออกมา และทำให้สามารถย้ายงานแสดงผลบางส่วนหรือทั้งหมดไปยังเธรดอื่น (หรือเธรด) ได้
ตอนนี้ BlinkNG ใกล้จะเสร็จสมบูรณ์แล้ว เราจึงเริ่มสำรวจด้านนี้แล้ว การคอมมิตแบบไม่บล็อกเป็นการเริ่มต้นครั้งแรกในการเปลี่ยนแปลงรูปแบบการแยกเธรดของโปรแกรมแสดงผล การคอมมิตคอมโพสิเตอร์ (หรือเพียงแค่การคอมมิต) คือขั้นตอนการซิงค์ระหว่างเธรดหลักและเธรดคอมโพสิเตอร์ ในระหว่างการคอมมิต เราจะทำสำเนาข้อมูลการแสดงผลที่สร้างขึ้นบนเธรดหลักเพื่อให้โค้ดการคอมโพสดาวน์สตรีมที่ใช้ของเธรดคอมโพสเซอร์ ขณะดำเนินการซิงค์นี้ การดำเนินการของเธรดหลักจะหยุดลงขณะที่โค้ดการคัดลอกทำงานบนเธรดคอมโพสิเตอร์ การดำเนินการนี้ช่วยให้มั่นใจได้ว่าเธรดหลักจะไม่แก้ไขข้อมูลการแสดงผลขณะที่เธรดคอมโพสิตอร์กำลังคัดลอกข้อมูล
การคอมมิตแบบไม่บล็อกจะทำให้เทรดหลักไม่จําเป็นต้องหยุดและรอให้ระยะการคอมมิตสิ้นสุดลง เทรดหลักจะทํางานต่อไปขณะที่การคอมมิตทํางานพร้อมกันในเทรดคอมโพสิเตอร์ ผลสุทธิของ Non-Blocking Commit คือการลดเวลาที่ใช้ในการแสดงผลในเทรดหลัก ซึ่งจะลดความแออัดในเทรดหลักและปรับปรุงประสิทธิภาพ ขณะเขียนบทความนี้ (มีนาคม 2022) เรามีโปรโตไทป์ที่ใช้งานได้ของ Non-Blocking Commit และกำลังเตรียมทำการวิเคราะห์โดยละเอียดเกี่ยวกับผลกระทบต่อประสิทธิภาพ
ฟีเจอร์ที่รอเปิดตัวคือการคอมโพสนอกเธรดหลัก โดยมีเป้าหมายเพื่อทําให้เครื่องมือแสดงผลตรงกับภาพโดยย้ายการจัดเลเยอร์ออกจากเธรดหลักไปยังเธรดผู้ทํางาน เช่นเดียวกับการคอมมิตแบบไม่บล็อก การดำเนินการนี้จะลดความแออัดในเทรดหลักด้วยการเพิ่มปริมาณงานการแสดงผล โปรเจ็กต์เช่นนี้คงเกิดขึ้นไม่ได้หากไม่มีสถาปัตยกรรมที่ปรับปรุงใหม่ของคอมโพสิตหลังทาสี
และยังมีโปรเจ็กต์อื่นๆ อีกมากมายที่กำลังจะเกิดขึ้น ในที่สุดเราก็มีรากฐานที่ทำให้เราทดลองแจกจ่ายงานแสดงผลอีกครั้งได้ และเราตื่นเต้นมากที่จะได้ดูว่าจะเกิดอะไรขึ้น