הכלי Profile GPU Rendering מציין את הזמן היחסי שכל שלב בצינור העיבוד לוקח כדי לעבד את הפריים הקודם. הידע הזה יכול לעזור לכם לזהות צווארי בקבוק בצינור, כדי שתוכלו לבצע אופטימיזציה לשיפור ביצועי העיבוד של האפליקציה.
בדף הזה מוסבר בקצרה מה קורה בכל שלב בצינור העיבוד, ומוצגות בעיות שעלולות לגרום לצווארי בקבוק. לפני שקוראים את הדף הזה, כדאי להכיר את המידע שמופיע במאמר מהירות העיבוד של ה-GPU בפרופיל. בנוסף, כדי להבין איך כל השלבים משתלבים זה בזה, כדאי לעיין במאמר בנושא איך צינור העיבוד פועל.
ייצוג חזותי
הכלי Profile GPU Rendering מציג שלבים וזמנים יחסיים בצורה של תרשים: היסטוגרמה עם קידוד צבעים. איור 1 מציג דוגמה לתצוגה כזו.
כל מקטע בכל עמודה אנכית שמוצגת בתרשים Profile GPU Rendering מייצג שלב בצינור העיבוד ומודגש באמצעות צבע ספציפי בתרשים העמודות. באיור 2 מוצג מקרא עם המשמעות של כל צבע שמוצג.
אחרי שמבינים מה המשמעות של כל צבע, אפשר לטרגט היבטים ספציפיים באפליקציה כדי לנסות לבצע אופטימיזציה של ביצועי העיבוד שלה.
השלבים והמשמעויות שלהם
בקטע הזה מוסבר מה קורה בכל שלב, וגם מהן הסיבות לצווארי בקבוק שכדאי לשים לב אליהן.
טיפול בקלט
בשלב הטיפול בקלט בצינור, נמדד משך הזמן שהאפליקציה השקיעה בטיפול באירועי קלט. המדד הזה מציין כמה זמן האפליקציה השקיעה בהרצת קוד שנקרא כתוצאה מקריאות חוזרות של אירועי קלט.
כשהפלח הזה גדול
ערכים גבוהים באזור הזה הם בדרך כלל תוצאה של עבודה רבה מדי או עבודה מורכבת מדי שמתרחשת בתוך קריאות חוזרות (callbacks) של אירועים של input-handler. הקריאות החוזרות האלה תמיד מתרחשות בשרשור הראשי, ולכן הפתרונות לבעיה הזו מתמקדים באופטימיזציה של העבודה ישירות או בהעברת העבודה לשרשור אחר.
בשלב הזה יכול להיות שיופיעו גם גלילה בLazyColumn או בLazyRow. אחרי שמגע של משתמש נחשב לגלילה, הרכיב lazy
list צורך אירועי מגע כדי ליצור ולסדר פריטים באופן דינמי. אם האפליקציה מבצעת פעולות מותאמות אישית בתגובה לשינויים במיקום הגלילה, חשוב שהפעולה הזו תתבצע כמה שיותר מהר כדי למנוע ירידה בקצב הפריימים. כדי לבצע בדיקה מעמיקה יותר, אפשר להשתמש בכלי פרופילים כמו CPU Profiler ב-Android Studio או Perfetto. מידע נוסף זמין במאמר סקירה כללית של מעקב אחר המערכת.
אנימציות
בשלב האנימציות מוצג משך הזמן שנדרש להערכת כל מצבי האנימציה שהופעלו בפריים הזה. חלק מממשקי ה-API הנפוצים לאנימציה ב-Compose הם animate*AsState, Transition ו-Animatable.
בנוסף, במהלך השלב הזה מופעל Recomposer כדי לעבד שינויים במצב של תמונת מצב ולעדכן קומפוזיציות. המשמעות היא שעלויות התקורה של הרה-קומפוזיציה מופיעות בדרך כלל ישירות בשלב האנימציה.
בממשקי משתמש של Jetpack Compose, כדאי לכלול את ספריית Compose Runtime Tracing כדי לראות עקבות מפורטים של יצירה לצד אירועים במערכת.
כשהפלח הזה גדול
ערכים גבוהים באזור הזה הם בדרך כלל תוצאה של עבודה שמתבצעת בגלל שינויים במצב שנובעים מהאנימציה. לדוגמה, אנימציה של הטלה, שגורמת לגלילה של LazyColumn
או של LazyRow, גורמת ליצירה מהירה, למדידה ולהקצאה של פריטים חדשים ברשימה.
מדידה
כדי לצייר את הרכיבים הקומפוזביליים במסך, מערכת Android מבצעת שלושה שלבים בצמתי הפריסה בעץ ממשק המשתמש.
קודם כל, המערכת מודדת את צמתי הפריסה. לכל רכיב שאפשר להרכיב יש אילוצים ומאפיינים ספציפיים שמתארים את מגבלות הגודל של האובייקט במסך. לחלק מהרכיבים הקומפוזביליים יכול להיות גודל ספציפי וקבוע, ולחלק מהם יש גודל שמותאם למגבלות שמועברות מהקומפוזיציה של פריסת האב.
בשלב השני, המערכת ממקמת את צמתי הפריסה. אחרי ש-Compose מחשב את הגדלים של צמתי הצאצא במהלך שלב המדידה, הוא יכול להמשיך לשלב המיקום, שבו הוא קובע את הגודל והמיקום של צמתי הפריסה במסך.
המערכת תמיד מבצעת פריסה של מעבר יחיד כדי לשפר את היעילות. כשפריסת הניתנת להרכבה נפסלת, Compose מודד את הצומת הספציפי הזה ומפיץ את עדכוני הפריסה רק עד להיררכיות של ההורה אם גודל הצאצא או האילוצים שלו משתנים.
כשהפלח הזה גדול
פלח גדול באזור הזה מצביע על כך שהאפליקציה מבלה יותר מדי זמן בשלב הפריסה, שכולל מיקום וקביעה של גודל צמתי הפריסה. הפעולות האלה כוללות ביצוע של שינויים במדידה ובמיקום של רכיבים שניתנים להרכבה, מה שיכול לעכב את הכנת הפריים אם עץ הפריסה מורכב מדי. במקרים כאלה, כדי לשפר את הביצועים צריך להשוות את האפליקציה שלכם שנכתבה ב-Compose לאפליקציות אחרות ולפעול לפי השיטות המומלצות לשיפור הביצועים.
אפשר להשתמש בכלי CPU Profiler ב-Android Studio או ב-Perfetto כדי לבדוק את שלבי הפריסה ולזהות צווארי בקבוק. מידע נוסף זמין במאמר סקירה כללית של מעקב אחר המערכת.
ציור
בשלב הציור, פעולות העיבוד, כמו ציור של רקע, צורה או טקסט, מתורגמות לרצף של פקודות ציור מקוריות. המערכת מתעדת את הפקודות האלה ברשימת תצוגה לביצוע על ידי המעבד הגרפי.
בסרגל Draw נרשם כמה זמן לוקח להשלים את הלכידה של הפקודות ברשימת התצוגה, עבור כל צמתי הפריסה שנדרש לעדכן במסך עבור המסגרת הזו. הזמן שנמדד חל גם על לוגיקת ציור בהתאמה אישית שיש לכם בתוך משני ציור או רכיב Canvas.
כשהפלח הזה גדול
במילים פשוטות, המדד הזה מראה כמה זמן לקח להפעיל את כל פקודות הציור עבור כל צומת פריסה לא תקף.
המדידה הזו כוללת את הזמן שנדרש לשליחת הפקודות האלה לצמתים משניים ולנכסי וקטורים. לכן, אם אתם רואים עלייה חדה בגרף הזה, יכול להיות שהסיבה לכך היא שרכיבים רבים ניתנים להרכבה פתאום הפכו ללא תקפים. ביטול התוקף מחייב להריץ מחדש את פקודות הציור וליצור מחדש את רשימות התצוגה של צמתי הפריסה. לחלופין, יכול להיות שהזמן הארוך הוא תוצאה של כמה קומפוזיציות או קנבסים בהתאמה אישית, שכוללים לוגיקה מורכבת במיוחד בהטמעה שלהם.DrawScope
בנוסף, בדרך כלל Compose מטפל במעברים של המדידה והפריסה הפנימיים שלו במסגרת מה שהפלטפורמה מגדירה כשלב הציור. לכן, יכול להיות שסרגל הציור הגבוה נובע מפעולות יקרות או מוגזמות של מדידה או פריסה פנימית, ולא רק מפקודות ציור. במקרה של ספק, כדאי לצלם עקבות של Perfetto כדי לראות אם התקורה נובעת משגרות ציור או ממעברים של מדידה ופריסה של Compose.
העלאה
מדד ההעלאה מייצג את הזמן שנדרש להעברת אובייקטים של מפת סיביות מזיכרון ה-CPU לזיכרון ה-GPU במהלך הפריימים הנוכחיים.
למעבדים שונים, כמו CPU ו-GPU, יש אזורי RAM שונים שמוקדשים לעיבוד. כשמציירים מפת סיביות ב-Android, המערכת מעבירה את מפת הסיביות לזיכרון של ה-GPU לפני שה-GPU יכול לעבד אותה במסך. לאחר מכן, ה-GPU שומר את מפת הסיביות במטמון, כך שהמערכת לא צריכה להעביר את הנתונים שוב, אלא אם הטקסטורה נמחקת ממטמון הטקסטורה של ה-GPU.
הערה: במכשירי Lollipop, השלב הזה הוא סגול.
כשהפלח הזה גדול
כדי להשתמש במשאבים לציור פריים, כל המשאבים של פריים צריכים להיות בזיכרון ה-GPU. המשמעות היא שערך גבוה של המדד הזה יכול להצביע על מספר גדול של טעינות משאבים קטנים או על מספר קטן של משאבים גדולים מאוד. מקרה נפוץ הוא כשאפליקציה מציגה מפת סיביות יחידה שקרובה לגודל המסך. מקרה נוסף הוא כשאפליקציה מציגה מספר גדול של תמונות ממוזערות.
כדי להקטין את הסרגל הזה, אפשר להשתמש בטכניקות כמו:
- מוודאים שהרזולוציות של מפות הביטים לא גדולות בהרבה מהגודל שבו הן יוצגו. לדוגמה, לא כדאי להציג תמונה בגודל 1024x1024 כתמונה בגודל 48x48.
- כדאי לנצל את היתרונות של ספריות מודרניות כמו Coil כדי להעלות מראש מפת סיביות באופן אסינכרוני לפני שלב הסנכרון הבא.
הפעלת פקודות
הפלח issue commands (הנפקת פקודות) מייצג את הזמן שלוקח להנפיק את כל הפקודות שנדרשות לציור רשימות התצוגה במסך.
כדי שהמערכת תצייר רשימות תצוגה למסך, היא שולחת את הפקודות הנדרשות ל-GPU. בדרך כלל, הפעולה הזו מתבצעת באמצעות OpenGL ES API.
התהליך הזה לוקח זמן, כי המערכת מבצעת טרנספורמציה סופית וחיתוך לכל פקודה לפני שהיא שולחת את הפקודה ל-GPU. לאחר מכן נוצרת תקורה נוספת בצד של ה-GPU, שמחשב את הפקודות הסופיות. הפקודות האלה כוללות טרנספורמציות סופיות וחיתוך נוסף.
כשהפלח הזה גדול
הזמן שמוקצה לשלב הזה הוא מדד ישיר של המורכבות והכמות של רשימות התצוגה שהמערכת מעבדת בפריים נתון. לדוגמה, אם יש הרבה פעולות ציור, במיוחד במקרים שבהם יש עלות קטנה לכל פרימיטיב של ציור, יכול להיות שהזמן הזה יהיה ארוך יותר. לדוגמה:
for (i in 0 until 1000) { canvas.drawPoint() }
הנפקת כרטיס כזה יקרה בהרבה מהנפקת כרטיסים מהסוגים הבאים:
canvas.drawPoints(thousandPointArray)
לא תמיד יש קורלציה של 1:1 בין פקודות הנפקה לבין ציור בפועל של רשימות תצוגה. בניגוד לסרגל הפקודות של הבעיה, שמתעד את הזמן שנדרש לשליחת פקודות ציור ל-GPU, מדד הציור מייצג את הזמן שנדרש לתיעוד הפקודות שהונפקו ברשימת התצוגה.
ההבדל הזה נובע מכך שהמערכת שומרת במטמון את רשימות התצוגה בכל מקום שאפשר. כתוצאה מכך, יש מצבים שבהם גלילה, שינוי או אנימציה מחייבים את המערכת לשלוח מחדש רשימת תצוגה, אבל לא לבנות אותה מחדש – כלומר, לא לתעד מחדש את פקודות הציור – מאפס. כתוצאה מכך, יכול להיות שתראו סרגל גבוה של פקודות בעיה בלי לראות סרגל גבוה של פקודות ציור.
החלפת מאגרי נתונים זמניים
אחרי ש-Android מסיים לשלוח את רשימת התצוגה אל ה-GPU, המערכת מנפיקה פקודה סופית אחת כדי להודיע למנהל ההתקן של הגרפיקה שהיא סיימה עם המסגרת הנוכחית. בשלב הזה, מנהל ההתקן יכול סוף סוף להציג את התמונה המעודכנת במסך.
כשהפלח הזה גדול
חשוב להבין שה-GPU מבצע עבודה במקביל ל-CPU. מערכת Android מנפיקה פקודות ציור ל-GPU ואז עוברת למשימה הבאה. ה-GPU קורא את פקודות הציור האלה מתור ומעבד אותן.
במצבים שבהם יחידת העיבוד המרכזית (CPU) מנפיקה פקודות מהר יותר מיחידת העיבוד הגרפי (GPU), תור התקשורת בין המעבדים עלול להתמלא. במקרה כזה, המעבד נחסם וממתין עד שיהיה מקום בתור כדי להציב את הפקודה הבאה. מצב התור המלא הזה מתרחש לעיתים קרובות בשלב של החלפת המאגרים, כי בשלב הזה נשלחו פקודות ששקולות למסגרת שלמה.
כדי לפתור את הבעיה הזו, צריך לצמצם את מורכבות העבודה שמתבצעת במעבד הגרפי, בדומה למה שעושים בשלב של פקודות הבעיה.
שונות
בנוסף לזמן שנדרש למערכת העיבוד כדי לבצע את העבודה שלה, יש עוד עבודה שמתבצעת בשרשור הראשי ולא קשורה לעיבוד. הזמן שמוקדש לעבודה הזו מדווח כזמן אחר. זמן שונות בדרך כלל מייצג עבודה שעשויה להתרחש בשרשור UI בין שני פריימים עוקבים של רינדור.
כשהפלח הזה גדול
אם הערך הזה גבוה, סביר להניח שיש באפליקציה קריאות חוזרות (callback), כוונות (intent) או פעולות אחרות שצריכות להתבצע בשרשור אחר. כלים כמו CPU Profiler ב-Android Studio או Perfetto יכולים לספק תובנות לגבי המשימות שפועלות בשרשור הראשי. המידע הזה יכול לעזור לכם לטרגט שיפורים בביצועים. מידע נוסף זמין במאמר סקירה כללית של מעקב אחר המערכת.