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

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

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

איור 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 נמוך יותר שיש לו שכבה מאוחדת (כלומר, הוא מוצג על גבי שכבה מורכבת)

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

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

איור 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, ואם הציור ארוך הוא גורם לשיבושים או לעיכובים באנימציות.

אפשר לראות, למשל, את התצוגה הזו של ציר הזמן של 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>

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

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

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

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

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

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

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

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

סיכום: DOM למסך

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

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

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

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

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

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

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