עיבוד מואץ ב-Chrome

מודל השכבות

Tom Wiltzius
Tom Wiltzius

מבוא

לרוב מפתחי האינטרנט, המודל הבסיסי של דף אינטרנט הוא ה-DOM. עיבוד הוא התהליך, שלרוב לא ברור, של הפיכת הייצוג הזה של הדף לתמונה במסך. בשנים האחרונות, דפדפנים מודרניים שינו את אופן הפעולה של העיבוד כדי לנצל את כרטיסי הגרפיקה: לרוב קוראים לזה באופן כללי 'האצהרת חומרה'. כשמדברים על דף אינטרנט רגיל (כלומר לא Canvas2D או WebGL), מה המשמעות האמיתית של המונח הזה? במאמר הזה נסביר על המודל הבסיסי שעליו מבוסס רינדור של תוכן אינטרנט ב-Chrome שמואץ באמצעות חומרה.

אזהרות גדולות וחשובות

אנחנו מדברים כאן על WebKit, ובאופן ספציפי יותר על יצירת ה-port של Chromium ל-WebKit. המאמר הזה עוסק בפרטי ההטמעה של Chrome, ולא בתכונות של פלטפורמת האינטרנט. פלטפורמת האינטרנט והסטנדרטים שלה לא מקודדים את רמת הפירוט הזו של ההטמעה, ולכן אין ערובה לכך שכל מה שמופיע במאמר הזה יחול על דפדפנים אחרים. עם זאת, ידע על הרכיבים הפנימיים יכול להיות שימושי לניפוי באגים מתקדם ולכוונון ביצועים.

כמו כן, חשוב לזכור שהמאמר כולו עוסק בחלק מרכזי בארכיטקטורת הרינדור של Chrome, שמשתנה במהירות רבה. במאמר הזה אנחנו מנסים לכלול רק דברים שלא צפויים להשתנות, אבל אין ערובה שהכול עדיין יחול בעוד שישה חודשים.

חשוב להבין של-Chrome יש כבר זמן מה שתי דרכים שונות לעיבוד: הדרך המואצת בחומרה והדרך הישנה יותר בתוכנה. נכון למועד כתיבת שורות אלה, כל הדפים עוברים את הנתיב המואץ בחומרה ב-Windows, ב-ChromeOS וב-Chrome ל-Android. ב-Mac וב-Linux, רק דפים שחלק מהתוכן שלהם צריך עיבוד קומפוזיציה עוברים את הנתיב המהיר (מידע נוסף על עיבוד קומפוזיציה מפורט בהמשך), אבל בקרוב כל הדפים יעברו את הנתיב המהיר גם שם.

לסיום, נציג סקירה כללית על מנוע הרינדור ונבחן את המאפיינים שלו שיש להם השפעה רבה על הביצועים. כשמנסים לשפר את הביצועים של האתר שלכם, כדאי להבין את מודל השכבות, אבל קל גם לפגוע בעצמכם: שכבות הן מבנים שימושיים, אבל יצירת הרבה שכבות עלולה להגדיל את התקורה בכל סטאק הגרפיקה. רצינו להזהיר אותך.

מה-DOM למסך

חדש – שכבות

אחרי שדף נטען ומנותח, הוא מיוצג בדפדפן כמבנה שידוע למפתחי אתרים רבים: DOM. עם זאת, כשדף עובר עיבוד, בדפדפן יש סדרה של ייצוגים ביניים שלא נחשפים ישירות למפתחים. המבנה החשוב ביותר הוא שכבה.

ב-Chrome יש למעשה כמה סוגים שונים של שכבות: שכבות עיבוד (RenderLayers), שאחראיות על עצים משניים של DOM, ושכבות גרפיקה (GraphicsLayers), שאחראיות על עצים משניים של שכבות עיבוד. השכבה האחרונה היא העניין העיקרי שלנו כאן, כי שכבות הגרפיקה הן אלה שמועמדות ל-GPU כטקסטורות. מעכשיו והלאה אשתמש במילה 'שכבה' כדי להתייחס ל-GraphicsLayer.

הערה קצרה על מונחי GPU: מהי טקסטורה? אפשר לחשוב על זה כתמונה בפורמט bitmap שמועברת מהזיכרון הראשי (כלומר זיכרון RAM) לזיכרון הווידאו (כלומר VRAM, ב-GPU). אחרי שהנתונים נמצאים ב-GPU, אפשר למפות אותם לגיאומטריה של רשת (mesh). בטכנולוגיית הווידאו או בתוכנות CAD, משתמשים בשיטה הזו כדי לתת 'עור' למודלים של שלדים תלת-ממדיים. ב-Chrome משתמשים בטקסטורות כדי להעביר קטעי תוכן של דפי אינטרנט ל-GPU. אפשר למפות טקסטורות למיקומים ולטרנספורמציות שונים בזול על ידי החלת הטקסטורות על רשת מלבנית פשוטה מאוד. כך פועל CSS תלת-ממדי, והוא מצוין גם לגלילה מהירה – אבל נרחיב על שני הנושאים האלה בהמשך.

נבחן כמה דוגמאות כדי להמחיש את מושג השכבות.

כשבודקים שכבות ב-Chrome, שימושי מאוד להשתמש בדגל 'הצגת גבולות של שכבות מורכבות' בהגדרות (סמל גלגל השיניים הקטן) בכלי הפיתוח, בקטע 'עיבוד'. הוא מדגיש בצורה פשוטה מאוד את המיקום של השכבות במסך. נפעיל את התכונה. צילומי המסך והדוגמאות האלה נלקחו מגרסת Chrome Canary העדכנית, Chrome 27 נכון למועד כתיבת המאמר.

איור 1: דף עם שכבה אחת

<!doctype html>
<html>
<body>
  <div>I am a strange root.</div>
</body>
</html>
צילום מסך של גבולות רינדור של שכבה מורכבת סביב שכבת הבסיס של הדף
צילום מסך של גבולות רינדור של שכבה מורכבת סביב שכבת הבסיס של הדף

לדף הזה יש רק שכבה אחת. התצוגה הכחולה מייצגת משבצות, שאפשר לחשוב עליהן כעל יחידות משנה של שכבה שבהן Chrome משתמש כדי להעלות חלקים של שכבה גדולה בכל פעם ל-GPU. הם לא ממש חשובים כאן.

איור 2: רכיב בשכבה משלו

<!doctype html>
<html>
<body>
  <div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
    I am a strange root.
  </div>
</body>
</html>
צילום מסך של גבולות הרינדור של שכבה מסובבת
צילום מסך של גבולות הרינדור של שכבה מסובבת

אם נוסיף למאפיין <div> מאפיין CSS תלת-ממדי שגורם לו להתנועע, נוכל לראות איך נראה רכיב עם שכבה משלו: שימו לב לגבול הכתום שמציין את השכבה בתצוגה הזו.

קריטריונים ליצירת שכבות

מה עוד מקבל שכבה משלו? שיטת הניתוח ההשוואתי של Chrome התפתחה לאורך זמן וממשיכה להתפתח, אבל נכון לעכשיו כל אחד מהאירועים הבאים יכול לגרום ליצירת שכבה:

  • מאפייני CSS של טרנספורמציה תלת-ממדית או טרנספורמציה לפי נקודת מבט
  • רכיבי <video> באמצעות פענוח וידאו מואץ
  • רכיבי <canvas> עם הקשר תלת-ממדי (WebGL) או הקשר דו-ממדי מואץ
  • יישומי פלאגין מורכבים (למשל, Flash)
  • רכיבים עם אנימציית CSS לשקיפות שלהם או באמצעות טרנספורמציה מונפשת
  • רכיבים עם מסנני CSS מאיצים
  • לאלמנט יש צאצא שיש לו שכבת קומפוזיציה (כלומר, אם לאלמנט יש רכיב צאצא שנמצא בשכבה משלו)
  • לאלמנט יש אח עם אינדקס-z נמוך יותר שיש לו שכבת עיבוד (כלומר, הוא עבר עיבוד מעל שכבה מורכבת)

השלכות מעשיות: אנימציה

אפשר גם להזיז את השכבות, ולכן הן שימושיות מאוד ליצירת אנימציה.

איור 3: שכבות עם אנימציה

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div>I am a strange root.</div>
</body>
</html>

כפי שצוין קודם, שכבות הן שימושיות מאוד לתנועה בתוכן סטטי באינטרנט. במקרה הבסיסי, Chrome מצייר את התוכן של שכבה בתוכנה בפורמט bitmap לפני ההעלאה שלו ל-GPU כטקסטורה. אם התוכן הזה לא ישתנה בעתיד, לא תצטרכו לצבוע אותו מחדש. זה דבר טוב: צביעה מחדש דורשת זמן שאפשר להקדיש לדברים אחרים, כמו הפעלת JavaScript, ואם תהליך הצביעה ארוך, הוא גורם להשהיות או לבעיות באנימציות.

לדוגמה, בתצוגה הזו של ציר הזמן של Dev Tools: אין פעולות צביעה בזמן שהשכבה הזו מסתובבת הלוך ושוב.

צילום מסך של ציר הזמן של Dev Tools במהלך אנימציה
צילום מסך של ציר הזמן של Dev Tools במהלך אנימציה

לא תקין! צביעה מחדש

אבל אם תוכן השכבה ישתנה, צריך לצבוע אותה מחדש.

איור 4: צביעה מחדש של שכבות

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div id="foo">I am a strange root.</div>
  <input id="paint" type="button" value="repaint">
  <script>
    var w = 200;
    document.getElementById('paint').onclick = function() {
      document.getElementById('foo').style.width = (w++) + 'px';
    }
  </script>
</body>
</html>

בכל פעם שלוחצים על רכיב הקלט, הרכיב המסתובב מתרחב ב-1px. כתוצאה מכך, מתבצעת פריסה מחדש וצביעת מחדש של הרכיב כולו, שהוא במקרה הזה שכבה שלם.

דרך טובה לראות מה מופיע בציור היא באמצעות הכלי 'הצגת ריבועים של ציור' בכלי הפיתוח, גם בקטע 'עיבוד' בהגדרות של כלי הפיתוח. אחרי ההפעלה, שימו לב שהאלמנט האנימציה והלחצן מהבהבים בצבע אדום כשמקישים על הלחצן.

צילום מסך של תיבת הסימון &#39;הצגת מלבנים עם הבזקי צבע&#39;
צילום מסך של תיבת הסימון 'הצגת ריבועים של משיכות צבע'

אירועי הצביעה מופיעים גם בציר הזמן של DevTools. קוראים עם עיניים חדות עשויים להבחין שיש כאן שני אירועי צביעה: אחד לשכבה ואחד ללחצן עצמו, שצובע מחדש כשהוא עובר למצב לחיצה או ממנו.

צילום מסך של ציר הזמן של Dev Tools שצובע מחדש שכבה
צילום מסך של ציר הזמן בכלי הפיתוח, שבו שכבה מסוימת נצבעת מחדש

חשוב לזכור ש-Chrome לא תמיד צריך לצבוע מחדש את כל השכבה. הוא מנסה לצבוע מחדש רק את החלק ב-DOM שהתוקף שלו בוטל. במקרה הזה, רכיב ה-DOM ששינינו הוא הגודל של השכבה כולה. אבל במקרים רבים אחרים, יהיו הרבה רכיבי DOM בשכבה.

השאלה הבאה המתבקשת היא מה גורם לביטול התוקף של פריטים ומאלץ צביעה מחדש. קשה לענות על השאלה הזו באופן מקיף כי יש הרבה מקרים קיצוניים שיכולים לאלץ ביטולים. הסיבה הנפוצה ביותר היא זיהום DOM על ידי מניפולציה בסגנונות CSS או ביצוע פריסה מחדש. ל-Tony Gentilcore יש פוסט מעולה בבלוג על הגורמים לפריסה מחדש, ול-Stoyan Stefanov יש מאמר שמסביר על ציור בפירוט רב יותר (אבל הוא מסתיים רק בציור, ולא בדברים האלה של שילוב שכבות).

הדרך הטובה ביותר לבדוק אם הבעיה משפיעה על משהו שאתם עובדים עליו היא להשתמש בכלים 'ציר הזמן' ו'הצגת ריבועים של צביעה' בכלי הפיתוח כדי לראות אם אתם מבצעים צביעה מחדש כשלא צריך, ואז לנסות לזהות איפה גרמתם ל-DOM להתלכלך ממש לפני העיצוב מחדש או הצביעה מחדש. אם הציור הוא בלתי נמנע אבל נראה שהוא נמשך זמן רב מדי, כדאי לעיין במאמר של Eberhard Gräther על מצב ציור רציף בכלי הפיתוח.

סיכום: DOM למסך

אז איך Chrome הופך את ה-DOM לתמונה במסך? מבחינה מושגית, הוא:

  1. המערכת מקבלת את ה-DOM ומחלקת אותו לשכבות
  2. ציור כל אחת מהשכבות האלה בנפרד בתמונות ביטמפי בתוכנה
  3. מעלים אותם ל-GPU כטקסטורות
  4. שילוב השכבות השונות לתמונה הסופית במסך.

כל זה צריך לקרות בפעם הראשונה ש-Chrome יוצר מסגרת של דף אינטרנט. אבל לאחר מכן, אפשר להשתמש בקיצורי דרך לכמה פרקים עתידיים:

  1. אם מאפייני CSS מסוימים משתנים, אין צורך לצבוע מחדש. Chrome יכול פשוט ליצור מחדש את השכבות הקיימות שכבר נמצאות ב-GPU כטקסטורות, אבל עם מאפייני קומפוזיציה שונים (למשל, במיקומים שונים, עם רמות שונות של אטימות וכו').
  2. אם חלק משכבה מסוימת לא תקף, הוא צויר מחדש והועלה מחדש. אם התוכן נשאר ללא שינוי אבל המאפיינים המשולבים שלו משתנים (למשל, הוא מתורגם או שהעכירות שלו משתנה), Chrome יכול להשאיר אותו ב-GPU ולבצע שילוב מחדש כדי ליצור פריים חדש.

עכשיו ברור למה למודל הקומפוזיציה המבוסס על שכבות יש השפעה משמעותית על ביצועי הרינדור. עיבוד קומפוזיציה הוא זול יחסית כשלא צריך לצייר שום דבר, לכן הימנעות מצביעת מחדש של שכבות היא יעד כללי טוב כשמנסים לנפות באגים בביצועי הרינדור. מפתחים מנוסים יעיינו ברשימת הטריגרים של עיבוד הקומפוזיציה שלמעלה ויבחינו שאפשר לאלץ בקלות את היצירה של שכבות. אבל חשוב להיזהר ולא ליצור אותם באופן עיוור, כי הם לא בחינם: הם תופסים זיכרון ב-RAM של המערכת וב-GPU (במיוחד במכשירים ניידים), ויצירה של הרבה מהם עלולה להוסיף עוד עלות לוגית לצורך מעקב אחרי הפריטים שגלויים. שכבות רבות יכולות גם להגדיל את משך הזמן של עיבוד הנתונים לתמונה רסטרית, אם השכבות גדולות ויש הרבה חפיפה ביניהן במקומות שבהם לא הייתה חפיפה בעבר. כתוצאה מכך, יכול להיות מצב שנקרא לפעמים 'ציור יתר'. לכן, חשוב להשתמש בידע שלכם בצורה חכמה.

זה הכול לבינתיים. בקרוב יפורסמו עוד כמה מאמרים על ההשלכות הפרקטיות של מודל השכבות.

מקורות מידע נוספים