กระบวนการทำงานภายในของโปรแกรมแสดงผล
บทความนี้เป็นส่วนที่ 3 จากซีรีส์บล็อก 4 ส่วนที่อธิบายวิธีการทำงานของเบราว์เซอร์ ก่อนหน้านี้เราได้พูดถึงสถาปัตยกรรมแบบหลายกระบวนการและขั้นตอนการนําทาง ในโพสต์นี้ เราจะมาดูสิ่งที่เกิดขึ้นภายในกระบวนการของโปรแกรมแสดงผล
กระบวนการของโปรแกรมแสดงผลเกี่ยวข้องกับประสิทธิภาพของเว็บในหลายๆ ด้าน เนื่องจากมีการดำเนินการหลายอย่างเกิดขึ้นภายในกระบวนการของโปรแกรมแสดงผล โพสต์นี้จึงเป็นเพียงภาพรวมทั่วไปเท่านั้น หากต้องการดูข้อมูลโดยละเอียด ส่วนประสิทธิภาพของ Web Fundamentals มีแหล่งข้อมูลอีกมากมาย
กระบวนการแสดงผลจะจัดการเนื้อหาเว็บ
กระบวนการแสดงผลมีหน้าที่รับผิดชอบต่อทุกสิ่งที่เกิดขึ้นในแท็บ ในกระบวนการแสดงผล เทรดหลักจะจัดการโค้ดส่วนใหญ่ที่คุณส่งไปยังผู้ใช้ บางครั้งเทรดเวิร์กจะจัดการ JavaScript บางส่วนหากคุณใช้ Web Worker หรือ Service Worker นอกจากนี้ ระบบจะเรียกใช้ชุดข้อความคอมโพสิตและแรสเตอร์ภายในกระบวนการแสดงผลเพื่อแสดงผลหน้าเว็บอย่างมีประสิทธิภาพและราบรื่น
งานหลักของกระบวนการแสดงผลคือเปลี่ยน HTML, CSS และ JavaScript เป็นหน้าเว็บที่ผู้ใช้โต้ตอบได้

การแยกวิเคราะห์
การสร้าง DOM
เมื่อกระบวนการแสดงผลได้รับข้อความการคอมมิตสำหรับการนําทางและเริ่มรับข้อมูล HTML ชุดข้อความหลักจะเริ่มแยกวิเคราะห์สตริงข้อความ (HTML) และเปลี่ยนให้เป็นโมเดลออบเจ็กต์เอกสาร (DOM)
DOM คือการแสดงหน้าเว็บภายในของเบราว์เซอร์ รวมถึงโครงสร้างข้อมูลและ API ที่นักพัฒนาเว็บโต้ตอบได้ผ่าน JavaScript
การสับเปลี่ยนเอกสาร HTML เป็น DOM กำหนดโดยมาตรฐาน HTML คุณอาจสังเกตเห็นว่าการส่ง HTML ไปยังเบราว์เซอร์ไม่เคยแสดงข้อผิดพลาด ตัวอย่างเช่น แท็ก </p>
ที่ปิดท้ายขาดหายไปเป็น HTML ที่ถูกต้อง มาร์กอัปที่ไม่ถูกต้อง เช่น Hi! <b>I'm <i>Chrome</b>!</i>
(ปิดแท็ก b ก่อนแท็ก i) ระบบจะถือว่าคุณเขียนว่า Hi! <b>I'm <i>Chrome</i></b><i>!</i>
เนื่องจากข้อกำหนด HTML ออกแบบมาเพื่อจัดการข้อผิดพลาดเหล่านั้นอย่างเหมาะสม หากสงสัยว่าสิ่งเหล่านี้ทำงานอย่างไร โปรดอ่านส่วน "ข้อมูลเบื้องต้นเกี่ยวกับการจัดการข้อผิดพลาดและกรณีที่แปลกประหลาดในโปรแกรมแยกวิเคราะห์" ของสเปค HTML
การโหลดทรัพยากรย่อย
โดยทั่วไปเว็บไซต์จะใช้ทรัพยากรภายนอก เช่น รูปภาพ, CSS และ JavaScript ไฟล์เหล่านั้นต้องโหลดจากเครือข่ายหรือแคช เทรดหลักอาจขอรายการเหล่านี้ทีละรายการเมื่อพบขณะแยกวิเคราะห์เพื่อสร้าง DOM แต่ระบบจะเรียกใช้ "เครื่องมือสแกนเพื่อโหลดล่วงหน้า" พร้อมกันเพื่อเพิ่มความเร็ว
หากมีสิ่งต่างๆ เช่น <img>
หรือ <link>
ในเอกสาร HTML เครื่องมือสแกนการโหลดล่วงหน้าจะดูที่โทเค็นที่สร้างขึ้นโดยโปรแกรมแยกวิเคราะห์ HTML และส่งคำขอไปยังเธรดเครือข่ายในกระบวนการเบราว์เซอร์

JavaScript สามารถบล็อกการแยกวิเคราะห์ได้
เมื่อตัวแยกวิเคราะห์ HTML พบแท็ก <script>
ก็จะหยุดแยกวิเคราะห์เอกสาร HTML ชั่วคราว และต้องโหลด แยกวิเคราะห์ และเรียกใช้โค้ด JavaScript สาเหตุคือ JavaScript สามารถเปลี่ยนรูปแบบของเอกสารได้โดยใช้สิ่งต่างๆ เช่น document.write()
ซึ่งจะเปลี่ยนโครงสร้าง DOM ทั้งหมด (ภาพรวมของรูปแบบการแยกวิเคราะห์ในข้อกำหนด HTML มีแผนภาพที่ยอดเยี่ยม) ด้วยเหตุนี้ โปรแกรมแยกวิเคราะห์ HTML จึงต้องรอให้ JavaScript ทำงานก่อนจึงจะแยกวิเคราะห์เอกสาร HTML ต่อได้ หากอยากรู้ว่าเกิดอะไรขึ้นในการเรียกใช้ JavaScript ทีม V8 มีบทบรรยายและบล็อกโพสต์เกี่ยวกับเรื่องนี้
บอกใบ้ให้เบราว์เซอร์ทราบวิธีที่คุณต้องการโหลดทรัพยากร
นักพัฒนาเว็บสามารถส่งคำแนะนำไปยังเบราว์เซอร์เพื่อโหลดทรัพยากรได้อย่างราบรื่นได้หลายวิธี
หาก JavaScript ไม่ได้ใช้ document.write()
คุณสามารถเพิ่มแอตทริบิวต์ async
หรือ defer
ลงในแท็ก <script>
จากนั้นเบราว์เซอร์จะโหลดและเรียกใช้โค้ด JavaScript แบบอะซิงโครนัสและไม่บล็อกการแยกวิเคราะห์ คุณยังใช้โมดูล JavaScript ได้หากเหมาะสม <link rel="preload">
เป็นวิธีแจ้งให้เบราว์เซอร์ทราบว่าจำเป็นต้องใช้ทรัพยากรดังกล่าวในการนําทางปัจจุบันและคุณต้องการดาวน์โหลดโดยเร็วที่สุด อ่านข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่การจัดลําดับความสําคัญของทรัพยากร – การใช้เบราว์เซอร์ให้เป็นประโยชน์
การคำนวณสไตล์
การมี DOM เพียงอย่างเดียวไม่เพียงพอที่จะทราบว่าหน้าเว็บจะมีลักษณะอย่างไร เนื่องจากเราสามารถจัดรูปแบบองค์ประกอบหน้าเว็บใน CSS ได้ เทรดหลักจะแยกวิเคราะห์ CSS และกำหนดสไตล์ที่คอมไพล์แล้วสำหรับโหนด DOM แต่ละโหนด ข้อมูลนี้บอกเกี่ยวกับประเภทสไตล์ที่ใช้กับองค์ประกอบแต่ละรายการตามตัวเลือก CSS คุณสามารถดูข้อมูลนี้ได้ในส่วน computed
ของเครื่องมือสำหรับนักพัฒนาเว็บ

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

เลย์เอาต์คือกระบวนการค้นหาเรขาคณิตขององค์ประกอบ เทรดหลักจะเรียกใช้ DOM และสไตล์ที่คอมไพล์แล้ว รวมถึงสร้างต้นไม้เลย์เอาต์ซึ่งมีข้อมูล เช่น พิกัด x, y และขนาดกล่องขอบเขต ต้นไม้เลย์เอาต์อาจมีโครงสร้างคล้ายกับต้นไม้ DOM แต่จะมีเฉพาะข้อมูลที่เกี่ยวข้องกับสิ่งที่ปรากฏในหน้าเท่านั้น หากใช้ display: none
องค์ประกอบนั้นจะไม่อยู่ในต้นไม้เลย์เอาต์ (แต่องค์ประกอบที่มี visibility: hidden
จะอยู่ในต้นไม้เลย์เอาต์) ในทำนองเดียวกัน หากมีการใช้องค์ประกอบจำลองที่มีเนื้อหา เช่น p::before{content:"Hi!"}
องค์ประกอบดังกล่าวจะรวมอยู่ในต้นไม้เลย์เอาต์แม้ว่าจะไม่ได้อยู่ใน DOM ก็ตาม

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

การมี DOM, สไตล์ และเลย์เอาต์ยังไม่เพียงพอที่จะแสดงผลหน้าเว็บ สมมติว่าคุณพยายามทำซ้ำภาพวาด คุณทราบขนาด รูปร่าง และตำแหน่งขององค์ประกอบแล้ว แต่ยังคงต้องตัดสินลำดับการวาด
เช่น อาจมีการตั้งค่า z-index
สําหรับองค์ประกอบบางอย่าง ซึ่งในกรณีนี้ การวาดตามลําดับขององค์ประกอบที่เขียนใน HTML จะทําให้การแสดงผลไม่ถูกต้อง

ในขั้นตอนการแสดงผลนี้ เทรดหลักจะเรียกใช้ต้นไม้เลย์เอาต์เพื่อสร้างเรคคอร์ดการแสดงผล บันทึกการวาดคือบันทึกกระบวนการวาด เช่น "วาดพื้นหลังก่อน แล้ววาดข้อความ แล้ววาดสี่เหลี่ยมผืนผ้า" หากคุณวาดบนองค์ประกอบ <canvas>
โดยใช้ JavaScript กระบวนการนี้อาจคุ้นเคยกับคุณ

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

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

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

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

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

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

ข้อดีของการคอมโพสคือการดําเนินการนี้ทำได้โดยไม่ต้องใช้เธรดหลัก ด้ายคอมโพสิตไม่จำเป็นต้องรอการคำนวณสไตล์หรือการดำเนินการ JavaScript ด้วยเหตุนี้ การคอมโพสิทเฉพาะภาพเคลื่อนไหวจึงถือว่าเหมาะที่สุดสำหรับประสิทธิภาพที่ราบรื่น หากต้องคำนวณเลย์เอาต์หรือการทาสีอีกครั้ง จะต้องเกี่ยวข้องกับเธรดหลัก
สรุป
ในโพสต์นี้ เราจะดูไปป์ไลน์การแสดงผลตั้งแต่การแยกวิเคราะห์ไปจนถึงการคอมโพส เราหวังว่าตอนนี้คุณจะมีข้อมูลในมือเพื่ออ่านเพิ่มเติมเกี่ยวกับการเพิ่มประสิทธิภาพของเว็บไซต์
ในโพสต์ถัดไปซึ่งเป็นโพสต์สุดท้ายของชุดนี้ เราจะดูรายละเอียดเพิ่มเติมเกี่ยวกับเธรดคอมโพสิต และดูสิ่งที่จะเกิดขึ้นเมื่อผู้ใช้ป้อนข้อมูล เช่น mouse move
และ click
คุณชอบโพสต์นี้ไหม หากมีข้อสงสัยหรือคำแนะนำสำหรับโพสต์ในอนาคต เรายินดีรับฟังจากคุณในส่วนความคิดเห็นด้านล่างหรือที่ @kosamari บน Twitter