- הפוסט משלים את נושא מנוע הרינדור מהפוסט הקודם (א.ה.ש.ה: “הדפדפן”).
- הפוסט משלים את הפוסטים על “ביצועים של אפליקציות אינטרנט” ונותן טיפים חשובים לשיפור זמני-רינדור בדפדפן.
פוסט זה שייך לסדרה אבני הבניין של האינטרנט.
במשך שנים נהגנו למדוד ביצועי אפליקציות ווב ע”פ:
- מספר ה round-trips בין הדפדפן לשרת – שאפנו בקנאות למינימום.
- כמות התעבורה ברשת ב kb – עמלנו על מנת לצמצם.
- CPU של תהליך הדפדפן – שייחסנו אותו ברובו למשהו שמעבר לשליטתנו.
באפליקציות ווב מודרניות המצב התהפך:
רוב זמן הרינדור הוא באופן ברור בצד הלקוח.
ע”פ Alexa, כ 80% מזמן רינדור הדף ב 1000 האתרים המובילים מושקע בצד הלקוח.
חלק נכבד מהשיפורים האפשריים – הוא בצד הלקוח.
לא מזמן נתקלתי במקרה בו הצליחו להוריד את זמן הרינדור של דף מ 11 ל 2.5 שניות ע”י שינוי בקוד הג’אווהסקריפט בלבד. זהו מקרה מעט קיצוני, אך אמיתי.
באפליקציות ווב, ניתן לשפר רבות את “הזרימה החלקה” של האפליקציה ע”י שינויים קטנים ב CSS ובקוד הג’אווהסקריפט שמשתמש ב DOM API – רק צריך להכיר כמה עקרונות שאסביר בפוסט זה. שינויים אלו משמעותיים במיוחד לשימוש ב touch בהם עיקובים ברינדור (“תקיעה”) היא מציקה במיוחד מכיוון שהציפיה היא שכל האפליקציה “תעבוד חלק”…
![]() |
מקור |
בעצם האלמנט החשוב ביותר היא תחושת המהירות של משתמש הקצה, שמושגת ע”י הצגת תוכן חלקי (“משהו”) מוקדם ככל האפשר. עקרון זה נקרא Perceived Performance והרשת מלאה בחומר בנושא, כך שאינני מרגיש שיש טעם שאוסיף.
![]() |
דף שעולה מהר, מול דף שנותן הרגשה שהוא עולה מהר. מקור. |
האם כל אופטימיזציות הרשת (כגון דחיסה ואיחוד של משאבים), שעסקנו בהן בפוסטים קודמים, הן מיותריות? כל וכלל לא. דווקא אופטימיזציות הרשת הן לרוב הקלות, אך עבור אפליקציות ווב – הן פשוט לא מספיקות. אציין גם שחלק מאופטימיזציות “הרשת“ משפיעות לטובה, באופן עקיף, על ביצועי הדפדפן.
מדוע רינדור הדף “עוצר” בזמן טעינת הדף?
![]() |
שלב 2: כאשר ה HTML Parser נתקל בתגיות המתארות קבצים אחרים – הוא טוען אותם. |
אלמנט בעייתי בשלב טעינת קבצי הג’אווהסקריפט הוא היכולת של הסקריפט לשנות את קובץ ה HTML – במיוחד בעזרת פקודת document.write. בפקודה זו, שנראית תמימה לכאורה, יש מלכוד: היא מוסיפה markup לסוף ה DOM Tree ולכן יש משמעות שונה אם נריץ אותה עכשיו, או בעוד כמה מילי-שניות, לאחר שעוד markup פורסר והוסף לסוף ה DOM Tree.
אפרופו: Internet Explorer, עד IE8, הגדיל לעשות והפסיק גם את טעינת הרשת של משאבים אחרים. עד היום לא ברור מדוע.
- להעביר את הבקשות לטעינת סקריפטים מה HEAD לתחתית ה HTML, ממש בסוף ה BODY [א].
- לציין על הסקריפטים ש “הם ילדים טובים” בעזרת תווית async (מייד)
- להשתמש בספריה כגון require.js שעושה את שני הדברים עבורכם [זהירות, פוסט].
- לא מבצעים כתיבה ל DOM בעזרת document.write בזמן ההרצה.
- סקריפטים אחרים לא תלויים בהם – בשלב ההרצה של הסקריפטים האחרים. כלומר הסקריפט האחר יכול לכלול פונקציות שקוראות לסקריפט שלנו, אל לא לפני שהיה ארוע document.ready.
אם הקוד שלכם מובנה ומסודר – קרוב לוודאי שזה כבר המצב.
המשמעות של תווית ה async היא שהדפדפן יטען את הקבצי הג’אווהסקריפט באופן מקבילי לפירסור HTML (ואחד לשני). כלומר – אין הבטחה באיזה סדר הם ירוצו. יש לנקוט בקוד הג’אווהסקריפט מעט מאמצי הגנה כגון הגדרת namespace בכל קובץ או וידוא שקוד ה bootstrap קשור לאירוע של הדפדפן (כגון document.ready) – לרוב זה לא מאמץ גדול.
תווית נוספת היא תווית defer שמאפשרת לסמן סקריפטים שיכולים לרוץ מאוחר יותר (כגון כפתור “feedback”). סקריפטים אלו יטענו במקביל כמו קבצים שסומנו כ async אך ירוצו רק כאשר כל משימות הפירסור הנוכחיות – הסתיימו.
HTML דינאמי – ההשפעה של פעולות DOM מתוך קוד ג’אווהסקריפט
![]() |
המשמעות של כתיבה ל DOM |
העלות היא, כמובן, אדירה – במיוחד כאשר מדובר באפליקציה גדולה.
דמיינו אפליקציה עם מאות nodes ב Style Tree. כל הכנסה של אלמנט ל DOM תדרוש מעבר על כל החוקים בתת העץ ב Render Tree. אם מבוצעות הכנסות חוזרות לאותו תת-עץ ב DOM Tree – יהיו בדיקות ונשנות של DOM Tree Nodes כנגד אותם חוקים – עבודה מיותרת בעליל.
מפתחים שמבינים שכתיבה ל DOM היא יקרה, עוברים בד”כ את השלב בו הם מנסים לבנות “תת עץ DOM” בצד ולהכניס אותו ב”מכה” אחת. למשל – כ String. פעולה זו לרוב לא תציג שיפור בתוצאות – מה שעלול להשאיר את המפתחים מבולבלים.
הסיבה שאנו לא רואים שיפור בין 2 הגישות היא מכיוון שכותבי הדפדפנים חשבו עלינו – ועשו את העבודה עבורנו:
כתיבות ל DOM Tree עוברות דרך Write Buffer שיבצע Flush (שפיכת כל השינויים שהצטברו ב buffer) בנקודת הזמן המאוחרת ביותר האפשרית.
מה שמעניין הוא מה היא” נקודת הזמן המאוחרת ביותר האפשרית”? ובכן – זו הנקודה בה קורה אחד מ 2 דברים:
- מישהו מבצע פעולת read מתוך ה DOM Tree או ה Render Tree – והדפדפן רוצה שהוא יקבל תשובה עדכנית לאור הכתיבות שנעשו קודם לכן.
- תזמון קבוע שנקבע בכדי להציג שינויים למשתמש. רוב הדפדפנים היום קבעו לעצמם קצת רענון של 60fps, כך שכל 16.67 מילי-שניות הדפדפן מעדכן את ה DOM בכדי שיוכל להציג את השינויים שנעשו על ה Canvas, כך שיגיע למשתמש הקצה.
אופטימיזציות שונות עשויות לזהות שפעולה אינה בעלת משמעות על המסך (למשל שינוי של אלמנט שהוגדר כ display:none) וכך לדלג על פעולת flush שנובעת מתזמון.
שימו לב שקריאות שונות של ה DOM API ניגשות למבני נתונים שונים. לדוגמה:
- קריאת getAttribute או innerHTML – תגרום לקריאה מה DOM Tree. (מסומנת כ “(Read (A” בתרשים למעלה)
- קריאת scrollHeight או scrollWidth (שהן פשוט הדרך לקבל את הגובה / רוחב של האלמנט) – תגרום לקריאה מה Render Tree. (מסומנת כ “(Read (B” בתרשים למעלה)
הערת צד: אל תגלו לאף אחד, אבל ה Render Tree הוא בעצם אינו עץ אחד אלא מספר מבני-נתונים בעלי חפיפה-חלקית שמרכיבים את התמונה השלמה. עובדה זו אינה תורמת לדיון – ולכן אשאר עם ההפשטה של ה “Render Tree”.
המסקנה מהדרך בה עובד ה DOM Tree Write Buffer היא חשובה:
- אם נבצע 100 כתיבות ל DOM ולאחר מכן 100 קריאות – תהיה פעולת Flush אחת.
- אם נבצע לולאה של 100 x (כתיבה + קריאה מה DOM) – יהיו 100 פעולות Flush.
דרכי קיצור ברינדור הדף: דילוגים על שלבים לא-נחוצים.
הדוגמה הקלה לתיאור היא פעולות ציור. כאשר אנו משנים צבע או רקע (צבע או תמונה) של אלמנט, אין צורך לחשב מחדש את הגובה / רוחב / מיקום של כל האלמנטים על הדף (==> Re-layout). אין גם צורך לבנות מחדש תתי-עצים ב Render Tree, כי אין סיכוי שהם יישתנו. אפשר לשנות רק את הצבע/רקע על האלמנט הספציפי ו”לקפוץ” ישר לרינדור ה Canvas.
הדפדפן מצדו ירנדר רק את ה”מלבן” בו היה השינוי, כך שברוב הפעמים זו פעולה פשוטה למדי. ניתן לראות אילו חלקים ב Canvas מרונדרים מחדש בעזרת כלי הפיתוח של כרום:
באופציות של כלי הפיתוח (חתכתי את התמונה רק לחלק הרלוונטי) יש לסמן את “Show Paint Rectangles” ואז כל אזור ב Canvas שצויר מחדש יופיע לשנייה מסומן במלבן אדום.
אם אתם רואים ששינוי קטן מבחינתכם גרר ציור של אזור גדול ב Canvas – כדאי לחקור את הסיבה.
פעולות המדלגות על כל שלבי הרינדור
הוספת class או attribute על אלמנט, כאשר class זה לא מחיל על האלמנט שום חוק חדש. עדיין יחושבו כל חוקי ה CSS מול האלמנט ותת העץ שלו, אך לא יבנו מחדש האלמנטים ב Render Tree, לא יהיה Layout ולא יהיה רינדור מחדש ל Canvas.
פעולות היכולות לדלג על ה Re-Layout
למשל:
- שינוי visibility (למשל none ל visible) – מכיוון שאלמנטים אלו עדיין נשמרים ב Render Tree.
- שינוי מיקום לאלמנט “מרחף” שאינו חלק מה Layout (או לפחות ה Layout שיושפע ממנו יהיה קטן בהרבה).
- פעולות transform
אולי כדאי לסכם פעולות אותן כדאי לנסות ולצמצם / לרכז כאשר עובדים על שיפור ביצועי ה rendering:
פעולות שיגרמו לשינויים ב Layout:
- שינוי גובה, רוחב
- הוספת אלמנטים / הסרת אלמנטים נראים.
- שינויים בטקסט (כולל, למשל, line-height או word-spacing)
- שינויים ב scroll
- שינויים ב border-width (אך לא בצבע או border-radius, אלו פעולות שישפיעו רק על ה render / paint)
- שינוי פונטים
- שינויים ב margin או padding
- שינוי Display מערך inline ל block (או להיפך)
פעולות שיגרמו לבניית תתי-עצים ב Render Tree
- Display (מעבר מ/אל “none”)
- Transform (מעבר מ/אל “none) – מכיוון שצריך לעדכן את הבנים
- float (מעבר מ/אל “none”)
סיכום
הנה כמה דברים מעשיים שאתם יכולים לעשות בכדי לשפר את ביצועי אפליקציית הווב שלכם:
- הדפדפנים כיום כוללים כלי פיתוח מתקדמים, בעיקר כרום ופיירפוקס. מומלץ ללמוד לעבוד עם Chrome Developer Tools ו/או Firefox Developer Tools. כאשר תשפרו ביצועים – רוב הזמן שלכם יילך לשם.
- כלים כמו YSlow או Page Speed יתנו לכם ניתוח מהיר, בעיקר על ביצועי הרשת.
- נסו להקדים את טעינת ה CSS להתחלה, ואת הרצת הסקריפטים לשלב מאוחר יותר.
- זכרו בעת כתיבת CSS שאת החוקים הדפדפן מנתח מימין לשמאל, ונסו לשים בצד ימין תנאי שקל לשלול.
- נסו לצמצם את מספר החוקים ב CSS ואת עומק ה DOM – במידת האפשר.
- היו מודעים ל DOM Tree Write buffer וכתבו קוד כך שלא יבצע flush ללא סיבה.
- למדו אילו פעולות גורמות לעבודה ב Rendering Engine ונסו להשתמש בפעולות שלהן יש דרכי קיצור.
- קראו את הפוסט על מנוע הג’אווהסקריפט – הוא כולל כמה טיפים נוספים.
שיהיה בהצלחה!
—–
[א] כלל זה עובד מצויין, יש לשים לב שאת קבצי ה CSS אנחנו רוצים לטעון מוקדם ככל הניתן (ז.א. ב HEAD) מכיוון של הוספה של Style Rule היא יקרה ביותר, במיוחד ככל שה DOM Tree הולך וגדל.
מקורות נוספים:
Faster HTML and CSS
http://www.youtube.com/watch?v=a2_6bGNZ7bA
צמצום פעולות ה Re-layout וה Paint
כולל דוגמאות קוד.
http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/
קורס מזורז לביצועי ווב
מצגת טובה!
http://www.igvita.com/slides/2012/webperf-crash-course.pdf
אחלה של מאמר, מעמיק ומקצועי..תודה רבה!
אחלה מאמרתודה רבה.
תמיד תענוג לקרא את המאמרים שלך כל פעם אתה מחדש לי המון