משתמשים ב-Simpleperf כדי להעריך את הביצועים של מכשיר. Simpleperf הוא כלי פרופיל מקורי לאפליקציות ולתהליכים מקומיים ב-Android. אפשר להשתמש ב-כלי לניתוחי מעבדים כדי לבדוק את השימוש של האפליקציה במעבד ואת פעילות השרשור בזמן אמת.
יש שני אינדיקטורים של ביצועים שגלויים למשתמשים:
- ביצועים צפויים ומורגשים. האם בממשק המשתמש (UI) יש ירידה בפריימים או שהוא מבצע רינדור באופן עקבי בקצב של 60FPS? האם האודיו מופעל ללא רעשי רקע או רעשי פופ? מהו משך העיכוב בין המגע של המשתמש במסך לבין הצגת האפקט במסך?
- משך הזמן הנדרש לביצוע פעולות ארוכות יותר (כמו פתיחת אפליקציות).
האפשרות הראשונה בולטת יותר מהאפשרות השנייה. בדרך כלל המשתמשים מבחינים בתנודות, אבל הם לא יוכלו להבחין בין זמן הפעלה של אפליקציה של 500 אלפיות שנייה לבין זמן הפעלה של 600 אלפיות שנייה, אלא אם הם יבדקו שני מכשירים זה לצד זה. זמן האחזור למגע מורגש באופן מיידי ומשפיע באופן משמעותי על התפיסה של המכשיר.
כתוצאה מכך, במכשיר מהיר צינור עיבוד הנתונים של ממשק המשתמש הוא הדבר החשוב ביותר במערכת, מלבד מה שדרוש כדי לשמור על פונקציונליות של צינור עיבוד הנתונים של ממשק המשתמש. כלומר, צינור עיבוד הנתונים של ממשק המשתמש צריך לדחות כל עבודה אחרת שלא נדרשת לממשק משתמש חלק. כדי לשמור על ממשק משתמש חלק, אם אפשר להריץ משימות של ממשק משתמש, צריך לעכב את הסנכרון ברקע, את שליחת ההתראות ואת משימות דומות. מותר להתפשר על הביצועים של פעולות ארוכות יותר (זמן ריצה של HDR+, הפעלת אפליקציה וכו') כדי לשמור על ממשק משתמש חלק.
קיבולת לעומת רעידות
כשבודקים את ביצועי המכשיר, שני מדדים משמעותיים הם הספק והתנודות.
קיבולת
הקיבולת היא הכמות הכוללת של משאב מסוים שיש במכשיר לאורך פרק זמן מסוים. יכולים להיות אלה משאבי מעבד (CPU), משאבי GPU, משאבי קלט/פלט, משאבי רשת, רוחב פס של זיכרון או כל מדד דומה. כשבודקים את הביצועים של המערכת כולה, כדאי להתייחס לרכיבים השונים באופן מופשט ולהניח שמדד אחד קובע את הביצועים (במיוחד כשמתבצעת התאמה של מכשיר חדש, כי עומסי העבודה שפועלים במכשיר הזה הם קבועים).
הקיבולת של מערכת משתנה בהתאם למשאבי המחשוב אונליין. שינוי התדר של המעבד או של המעבד הגרפי הוא הדרך העיקרית לשינוי הקיבולת, אבל יש דרכים נוספות, כמו שינוי מספר ליבות המעבד אונליין. לכן, הקיבולת של מערכת תואמת לצריכת החשמל שלה. שינוי הקיבולת תמיד גורם לשינוי דומה בצריכת החשמל.
הקיבולת הנדרשת בכל רגע נתון נקבעת בעיקר על ידי האפליקציה שפועלת. כתוצאה מכך, הפלטפורמה יכולה לעשות מעט מאוד כדי לשנות את הקיבולת הנדרשת לכל עומס עבודה נתון, והדרכים לעשות זאת מוגבלות לשיפורים בסביבת זמן הריצה (Android framework, ART, Bionic, GPU compiler/drivers, kernel).
רעידות
קל לראות את הקיבולת הנדרשת של עומס עבודה, אבל המושג 'רעידה' הוא מושג מעורפל יותר. למידע נוסף על תנודות קצב השעון כמכשול לקבלת ביצועים מהירים, מומלץ לקרוא את המאמר The Case of the Missing Supercomputer Performance: Achieving Optimal Performance on the 8,192 processors of ASCI Q. (המחקר הזה בוחן למה המחשב העל-רלוונטי ASCI Q לא השיג את הביצועים הצפויים, והוא מבוא מצוין לאופטימיזציה של מערכות גדולות).
בדף הזה נעשה שימוש במונח 'רעידה' כדי לתאר את מה שמכונה במאמר ASCI Q בשם רעש. רעידות הן התנהגות מערכת אקראית שמונעת את הפעלת העבודה. לרוב מדובר בעבודה שצריך להריץ, אבל יכול להיות שאין לה דרישות זמן קפדניות שמחייבות אותה לפעול בזמן מסוים. מכיוון שהיא אקראית, קשה מאוד להפריך את קיומו של רעידות בעומס עבודה נתון. בנוסף, קשה מאוד להוכיח שמקור ידוע של רעידות היה הגורם לבעיה מסוימת בביצועים. הכלים הנפוצים ביותר לאבחון הגורמים לתנודות (כמו מעקב או רישום ביומן) עלולים לגרום לתנודות משלהם.
מקורות של רעידות (jitter) שנובעים מהטמעות של Android בעולם האמיתי כוללים:
- עיכוב של מתזמן המשימות
- רכיבי handler להפסקות
- קוד הנהג פועל במשך זמן רב מדי כשהקצאת עדיפות או ההפרעות מושבתות
- softirqs ממושכים
- תחרות על נעילה (אפליקציה, מסגרת, מנהל ליבה, נעילת binder, נעילת mmap)
- תחרות על מתאר קובץ, שבה שרשור בעדיפות נמוכה מחזיק את הנעילה על קובץ, ומונע משרשור בעדיפות גבוהה לפעול
- הרצת קוד קריטי לממשק המשתמש בקטעי עבודה שבהם הוא עלול להתעכב
- מעברים של מעבדים (CPU) למצב חוסר פעילות
- רישום
- עיכובים בקלט/פלט
- יצירת תהליכים מיותרים (לדוגמה, שידורים של
CONNECTIVITY_CHANGE
) - טרחה מיותרת במטמון הדפים שנגרמת בגלל חוסר בזיכרון פנוי
משך הזמן הנדרש לתקופה נתונה של תנודות יכול לרדת או לא לרדת ככל שהקיבולת גדלה. לדוגמה, אם הנהג משאיר את ההפרעות מושבתות בזמן ההמתנה לקריאה ממערך ה-i2c, הזמן שיידרש יהיה קבוע, ללא קשר למהירות המעבד – 384 MHz או 2 GHz. הגדלת הקיבולת היא לא פתרון ריאלי לשיפור הביצועים כשיש תנודות באיכות האות. כתוצאה מכך, בדרך כלל מעבדים מהירים יותר לא ישפרו את הביצועים במצבים שבהם יש הגבלות על תנודות תדר (jitter).
לבסוף, בניגוד לקיבולת, התנודות בזמן האחזור נמצאות כמעט לחלוטין בתחום של ספק המערכת.
צריכת זיכרון
בדרך כלל, צריכת הזיכרון היא הגורם הבולט לבעיות בביצועים. צריכת הנתונים עצמה היא לא בעיית ביצועים, אבל היא עלולה לגרום לתנודות (jitter) בגלל תקורה של lowmemorykiller, הפעלות מחדש של השירות ושימוש מוגזם במטמון הדפים. צמצום צריכת הזיכרון יכול למנוע את הגורמים הישירים לביצועים חלשים, אבל יכולים להיות שיפורים ממוקדים אחרים שגם הם מונעים את הגורמים האלה (לדוגמה, הצמדת המסגרת כדי למנוע את ההוצאה שלה מהזיכרון, כי היא תוחזר אליו זמן קצר לאחר מכן).
ניתוח הביצועים הראשוניים של המכשיר
לא מומלץ להתחיל ממערכת שפועלת אבל הביצועים שלה נמוכים, ולנסות לתקן את התנהגות המערכת על ידי בדיקת מקרים ספציפיים של ביצועים נמוכים שגלויים למשתמשים. בדרך כלל קשה לשחזר ביצועים נמוכים (כלומר, רעידות) או בעיה באפליקציה, ולכן יש יותר מדי משתנים במערכת המלאה שמונעים מהשיטה הזו להיות יעילה. כתוצאה מכך, קל מאוד לזהות באופן שגוי את הגורמים ולבצע שיפורים קלים, תוך החמצה של הזדמנויות מערכתיות לשיפור הביצועים ברחבי המערכת.
במקום זאת, מומלץ להשתמש בגישה הכללית הבאה כשמפעילים מכשיר חדש:
- מפעילים את המערכת עד לממשק המשתמש, כשכל מנהלי ההתקנים פועלים ויש כמה הגדרות בסיסיות של פקטור הבקרה של התדר (אם משנים את הגדרות פקטור הבקרה של התדר, צריך לחזור על כל השלבים הבאים).
- מוודאים שהליבה תומכת בנקודת המעקב
sched_blocked_reason
וגם בנקודות מעקב אחרות בצינור עיבוד הנתונים של המסך, שמציינות מתי המסגרת נשלחת למסך. - יצירת מעקב ארוך של צינור עיבוד הנתונים של ממשק המשתמש כולו (מקבלת הקלט דרך IRQ ועד לסריקה הסופית) בזמן הרצת עומס עבודה קל ועקבי (לדוגמה, UiBench או בדיקת הכדור ב-TouchLatency).
- תיקון של ירידות בפריימים שזוהו בעומס העבודה הקליל והעקבי.
- חוזרים על שלבים 3-4 עד שאפשר להריץ את הסרטון בלי פריימים שהוחמצו למשך יותר מ-20 שניות בכל פעם.
- עוברים למקורות אחרים של תנודות גליות שגלויות למשתמש.
פעולות פשוטות נוספות שאפשר לבצע בשלב מוקדם של הפעלת המכשיר:
- מוודאים שהליבת המעבד כוללת את התיקון sched_blocked_reason tracepoint. נקודת המעקב הזו מופעלת באמצעות קטגוריית המעקב sched ב-systrace, והיא מספקת את הפונקציה שאחראית להעברה למצב שינה כשהשרשור נכנס למצב שינה ללא הפסקה. היא חיונית לניתוח הביצועים, כי שינה ללא הפסקה היא אינדיקטור נפוץ מאוד לתנודות.
- מוודאים שיש לכם מספיק מעקב אחרי צינורות עיבוד הנתונים של ה-GPU והתצוגה. ב-SOCs של Qualcomm מהדורות האחרונות, נקודות המעקב מופעלות באמצעות:
adb shell "echo 1 > /d/tracing/events/kgsl/enable"
adb shell "echo 1 > /d/tracing/events/mdss/enable"
האירועים האלה נשארים מופעלים כשמריצים את systrace, כך שאפשר לראות מידע נוסף על צינור עיבוד הנתונים של תצוגה (MDSS) בקטע mdss_fb0
. ב-SOCs של Qualcomm לא יופיע מידע נוסף על ה-GPU בתצוגה הרגילה של systrace, אבל התוצאות מופיעות ב-trace עצמו (פרטים נוספים זמינים במאמר הסבר על systrace).
מה שרוצים מהמעקב הזה אחרי המסך הוא אירוע יחיד שמציין ישירות שמסגרת נשלחה למסך. לאחר מכן תוכלו לקבוע אם הגעתם לזמן הפריימים הרצוי. אם האירוע Xn מתרחש תוך פחות מ-16.7 אלפיות השנייה אחרי האירוע Xn-1 (בהנחה שמדובר במסך של 60Hz), סימן שלא הייתה תנועה קטועה. אם ה-SOC לא מספק אותות כאלה, צריך לפנות לספק כדי לקבל אותם. קשה מאוד לנפות באגים של רעידות (jitter) בלי אות סופי על השלמת הפריים.
שימוש במדדי ביצועים סינתטיים
מדדי ביצועים סינתטיים מאפשרים לוודא שהפונקציונליות הבסיסית של המכשיר קיימת. עם זאת, לא מומלץ להתייחס לנקודות השוואה כאל מדד לביצועי המכשיר כפי שהם נתפסים.
על סמך ניסיון עם מעבדי SOC, הבדלים בביצועים של מעבדי SOC במבחני ביצועים סינתטיים לא משקפים הבדל דומה בביצועים של ממשק המשתמש (מספר הפריימים שהוחמצו, זמן הפריים ב-99 percentile וכו'). נקודות השוואה סינתטיות הן נקודות השוואה של קיבולת בלבד. התנודות באיכות האות משפיעות על הביצועים שנמדדים בנקודות השוואה האלה רק על ידי גזילה של זמן מהפעולה הראשית של נקודת השוואה. כתוצאה מכך, ציונים של מדדי ביצועים סינתטיים הם ברוב המקרים לא רלוונטיים כמדד של ביצועים כפי שהמשתמשים תופסים אותם.
נניח שיש שני מערכי SOC שפועלים ב-Benchmark X, שמרינדור 1,000 פריימים של ממשק משתמש ומדווחים על זמן הרינדור הכולל (ציון נמוך יותר הוא טוב יותר).
- SOC 1 מעבד כל פריים של ביצועי הסף X ב-10 אלפיות השנייה ומקבל ציון של 10,000.
- ב-SOC 2, 99% מהפריימים עוברים רינדור תוך 1 אלפית השנייה, אבל 1% מהפריימים עוברים רינדור תוך 100 אלפיות השנייה, והציון הוא 19,900 – ציון טוב בהרבה.
אם מדד העזרה מציין את הביצועים בפועל של ממשק המשתמש, לא ניתן יהיה להשתמש ב-SOC 2. בהנחה של קצב ריענון של 60Hz, ב-SOC 2 תהיה תנודות בפריים בכל 1.5 שניות של פעולה. לעומת זאת, SOC 1 (ה-SOC האיטי יותר לפי מדד X) יפעל בצורה חלקה.
שימוש בדוחות על באגים
דוחות באגים יכולים להיות מועילים לפעמים לניתוח ביצועים, אבל בגלל שהם כבדים מאוד, הם לא מועילים בדרך כלל לניפוי באגים של בעיות תנודות תנועה פתאומית (jank) זמניות. הם עשויים לספק רמזים לגבי הפעולות שהמערכת ביצעה בזמן נתון, במיוחד אם התנודות היו סביב מעבר בין אפליקציות (שמתועד בדוח באג). דוחות באגים יכולים גם להצביע על בעיה רחבה יותר במערכת שעלולה לצמצם את הקיבולת האפקטיבית שלה (למשל, הגבלת קצב העברת הנתונים כתוצאה מהתחממות או פיצול זיכרון).
שימוש ב-TouchLatency
כמה דוגמאות להתנהגות לא טובה מגיעות מ-TouchLatency, שהוא עומס העבודה המחזורי המועדף ב-Pixel וב-Pixel XL. הכלי זמין בכתובת frameworks/base/tests/TouchLatency
ויש לו שני מצבים: זמן אחזור למגע ו'כדור קופץ' (כדי לעבור בין המצבים, לוחצים על הלחצן בפינה הימנית העליונה).
בדיקת הכדור הקופץ פשוטה בדיוק כפי שהיא נראית: כדור קופץ במסך לנצח, ללא קשר להזנת המשתמש. בדרך כלל, זה גם הבדיקה הקשה ביותר להרצה מושלמת, אבל ככל שהבדיקה תתקרב להרצה ללא פריימים שהוחמצו, כך המכשיר יהיה טוב יותר. בדיקת כדור הקפיצה קשה כי היא עומס עבודה טריוויאלי אבל עקבי לחלוטין שפועל בקצב שעון נמוך מאוד (ההנחה היא שלמכשיר יש מנהל תדרים. אם המכשיר פועל במקום זאת עם שעונים קבועים, צריך להוריד את קצב השעון של המעבד/ה-GPU לקרוב למינימום כשמריצים את בדיקת כדור הקפיצה בפעם הראשונה). ככל שהמערכת נכנסת למצב רגיעה והשעונים מתקרבים למצב חוסר פעילות, הזמן הנדרש ל-CPU/GPU לכל פריים עולה. אפשר לצפות בכדור ולראות תנודות, וגם לראות פריימים חסרים ב-systrace.
מאחר שעומס העבודה עקבי מאוד, אפשר לזהות בקלות רבה יותר את רוב מקורות התנודות (jitter) בהשוואה לרוב עומסי העבודה שגלויים למשתמשים. לשם כך, עוקבים אחרי מה שמריצים במערכת במהלך כל פריים שהוחמצ, במקום אחרי צינור עיבוד הנתונים של ממשק המשתמש. שעונים איטיים יותר מגבירים את ההשפעות של תנודות קצב השעון, כי הם מגבירים את הסיכוי שתנודות כאלה יגרמו להפסקת העברת פריימים. כתוצאה מכך, ככל ש-TouchLatency קרוב יותר ל-60FPS, כך יש פחות סיכוי להתנהגויות מערכת לא טובות שגורמות לתנודות תנועה פתאומות (jerk) באפליקציות גדולות, שקשה לשחזר.
מאחר שתנודות קצב הנתונים (jitter) לרוב (אבל לא תמיד) לא משתנות בהתאם למהירות השעון, מומלץ להשתמש בבדיקות שפועלות במהירויות שעון נמוכות מאוד כדי לאבחן את תנודות קצב הנתונים מהסיבות הבאות:
- לא כל התנודות בזמן האות הן קבועות במהירות השעון. מקורות רבים פשוט צורכים זמן מעבד.
- כדי שהזמן הממוצע להצגת פריימים יהיה קרוב למועד היעד, ה-governor צריך להאט את השעון. לכן, הזמן שבו מתבצעת עבודה שאינה קשורה לממשק המשתמש עלול לגרום לכך שה-governor יפסיק להציג פריימים.