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

מודל השכבה

Tom Wiltzius
Tom Wiltzius

מבוא

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

אזהרות גדולות, שמנים

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

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

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

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

מה-DOM למסך

חדש: שכבות

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

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

מלבד המושגים של GPU: מה זה טקסטורה? אפשר לחשוב על זה בתור תמונה של מפת סיביות שמועברת מהזיכרון הראשי (כלומר RAM) לזיכרון הווידאו (כלומר VRAM, ב-GPU). כשמשלבים את ה-GPU ב-GPU, ניתן למפות אותו לגיאומטריה של רשת – במשחקי וידאו או בתוכניות 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>
צילום מסך של גבולות העיבוד של השכבה המסובבת
צילום מסך של גבולות העיבוד של השכבה המסובבת

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

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

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

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

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

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

איור 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 מצייר את תוכן השכבה במפת סיביות של תוכנה לפני העלאתה ל-GPU כמרקם. אם התוכן הזה לא ישתנה בעתיד, אין צורך לצבוע אותו מחדש. זה דבר טוב: צביעה מחדש לוקחת זמן שאפשר להשקיע בדברים אחרים, כמו הרצת JavaScript, ואם הצבע ארוך, זה גורם לשיבושים או לעיכובים באנימציות.

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

צילום מסך של ציר הזמן של כלי הפיתוח במהלך אנימציה
צילום מסך של ציר הזמן של כלי הפיתוח במהלך אנימציה

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

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

איור 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>

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

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

צילום מסך של תיבת הסימון של הצגת מלבני צבע
צילום מסך של תיבת הסימון 'הצגת מלבנים'

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

צילום מסך של ציור מחדש של שכבה בציר הזמן של כלי הפיתוח
צילום מסך של ציור מחדש של שכבה בציר הזמן של כלי הפיתוח

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

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

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

סיכום: DOM to Screen

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

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

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

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

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

זה הכול בינתיים. מחכים לכם עוד כמה מאמרים בנושא השלכות מעשיות של מודל השכבה.

משאבים נוספים