הערכת הביצועים

אפשר להשתמש ב-Simpleperf כדי להעריך את הביצועים של מכשיר. ‫Simpleperf הוא כלי פרופיל מובנה לאפליקציות ולתהליכים מובנים ב-Android. אפשר להשתמש ב-CPU Profiler כדי לבדוק את השימוש במעבד של האפליקציה ואת פעילות השרשור בזמן אמת.

יש שני אינדיקטורים של ביצועים שגלויים למשתמשים:

  • ביצועים צפויים ומורגשים. האם ממשק המשתמש משמיט פריימים או מבצע רינדור באופן עקבי ב-60FPS? האם האודיו מושמע ללא ארטיפקטים או רעשי פצפוץ? מהו העיכוב בין המגע של המשתמש במסך לבין הצגת האפקט בתצוגה?
  • משך הזמן שנדרש לפעולות ארוכות יותר (כמו פתיחת אפליקציות).

הראשון בולט יותר מהשני. בדרך כלל המשתמשים מבחינים בבעיות בממשק (jank), אבל הם לא יוכלו להבחין בין זמן הפעלה של 500 אלפיות השנייה לבין זמן הפעלה של 600 אלפיות השנייה, אלא אם הם מסתכלים על שני מכשירים זה לצד זה. השהיית המגע מורגשת באופן מיידי ותורמת באופן משמעותי לתפיסת המכשיר.

כתוצאה מכך, במכשיר מהיר, צינור הנתונים של ממשק המשתמש הוא הדבר הכי חשוב במערכת, מלבד מה שנדרש כדי לשמור על הפונקציונליות של צינור הנתונים של ממשק המשתמש. המשמעות היא שצינור עיבוד הנתונים של ממשק המשתמש צריך לקבל עדיפות על פני כל עבודה אחרת שלא נדרשת כדי שממשק המשתמש יפעל בצורה חלקה. כדי לשמור על ממשק משתמש חלק, צריך לדחות את הסנכרון ברקע, את העברת ההתראות ופעולות דומות אחרות אם אפשר להריץ פעולות בממשק המשתמש. אפשר להקריב את הביצועים של פעולות ארוכות יותר (זמן ריצה של HDR+, הפעלת אפליקציה וכו') כדי לשמור על ממשק משתמש חלק.

קיבולת לעומת תנודות

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

קיבולת

קיבולת היא הכמות הכוללת של משאב מסוים שיש למכשיר במשך פרק זמן מסוים. יכול להיות שמדובר במשאבי CPU, משאבי GPU, משאבי קלט/פלט, משאבי רשת, רוחב פס של זיכרון או כל מדד דומה. כשבודקים את הביצועים של המערכת כולה, כדאי להפריד בין הרכיבים השונים ולהניח שיש מדד אחד שקובע את הביצועים (במיוחד כשמבצעים אופטימיזציה למכשיר חדש, כי עומסי העבודה שמופעלים במכשיר הזה כנראה קבועים).

הקיבולת של מערכת משתנה בהתאם למשאבי המחשוב אונליין. שינוי התדירות של CPU או GPU הוא האמצעי העיקרי לשינוי הקיבולת, אבל יש אמצעים אחרים, כמו שינוי מספר ליבות ה-CPU אונליין. בהתאם לכך, הקיבולת של מערכת תואמת לצריכת החשמל שלה. שינוי הקיבולת תמיד יוביל לשינוי דומה בצריכת החשמל.

הקיבולת הנדרשת בזמן נתון נקבעת בעיקר על ידי האפליקציה שפועלת. כתוצאה מכך, הפלטפורמה יכולה לעשות מעט מאוד כדי להתאים את הקיבולת הנדרשת לעומס עבודה נתון, והאמצעים לעשות זאת מוגבלים לשיפורים בזמן הריצה (מסגרת Android, ‏ ART, ‏ Bionic, ‏ מהדר/מנהלי התקנים של GPU,‏ ליבת המערכת).

רעידות

קל לראות את הקיבולת הנדרשת לעומס עבודה, אבל ג'יטר הוא מושג יותר מעורפל. כדי לקבל מבוא טוב לבעיית הג'יטר כגורם שמפריע למערכות מהירות, מומלץ לקרוא את המאמר The Case of the Missing Supercomputer Performance: Achieving Optimal Performance on the 8,192 processors of ASCI Q. (זו חקירה של הסיבות לכך שהמחשב העל ASCI Q לא הגיע לביצועים הצפויים, והיא מהווה מבוא מצוין לאופטימיזציה של מערכות גדולות).

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

מקורות של ג'יטר שנחוו בהטמעות בעולם האמיתי של Android כוללים:

  • עיכוב בתזמון
  • רכיבי handler של הפרעות
  • קוד של מנהל התקן פועל יותר מדי זמן עם השבתה של קדימות או הפרעות
  • Long-running softirqs
  • תחרות על נעילה (אפליקציה, מסגרת, מנהל התקן של ליבת המערכת, נעילת binder, נעילת mmap )
  • מחלוקת על מתאר קובץ, שבה שרשור בעדיפות נמוכה מחזיק את הנעילה של קובץ, ומונע משרשור בעדיפות גבוהה לפעול
  • הפעלת קוד קריטי לממשק המשתמש בתורי עבודה שבהם יכול להיות עיכוב
  • מעברים למצב סרק של המעבד
  • רישום ביומן
  • עיכובים בקלט/פלט
  • יצירת תהליכים מיותרים (לדוגמה, שידורים של CONNECTIVITY_CHANGE)
  • העומס על מטמון הדפים נגרם בגלל חוסר בזיכרון פנוי

משך הזמן הנדרש לתקופה מסוימת של ג'יטר עשוי לקטון או לא לקטון ככל שהקיבולת גדלה. לדוגמה, אם דרייבר משאיר את ההפרעות מושבתות בזמן שהוא מחכה לקריאה מאוטובוס i2c, ייקח לו כמות קבועה של זמן, בלי קשר אם המעבד הוא 384 MHz או 2 GHz. הגדלת הקיבולת היא לא פתרון אפשרי לשיפור הביצועים כשמעורב ג'יטר. כתוצאה מכך, מעבדים מהירים יותר בדרך כלל לא ישפרו את הביצועים במצבים שבהם יש מגבלות על הג'יטר.

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

צריכת זיכרון

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

ניתוח הביצועים הראשוניים של המכשיר

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

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

  1. מפעילים את המערכת עד לממשק המשתמש כשכל מנהלי ההתקנים פועלים וכמה הגדרות בסיסיות של בקר תדירות מוגדרות (אם משנים את ההגדרות של בקר התדירות, צריך לחזור על כל השלבים שלמטה).
  2. מוודאים שהליבה תומכת בsched_blocked_reason tracepoint וב-tracepoints אחרים בצינור העיבוד של התצוגה שמציינים מתי המסגרת מועברת לתצוגה.
  3. מבצעים מעקב ארוך של כל צינור העיבוד של ממשק המשתמש (מקבלת קלט דרך IRQ ועד לסריקה הסופית) בזמן הפעלת עומס עבודה קל ועקבי (לדוגמה, UiBench או בדיקת הכדור ב-TouchLatency).
  4. צריך לתקן את הנשירות של הפריימים שזוהו בעומס העבודה הקל והעקבי.
  5. חוזרים על שלבים 3-4 עד שאפשר להריץ את הסרטון בלי אף פריימים שהושמטו למשך 20 שניות ומעלה בכל פעם.
  6. עוברים למקורות אחרים של ג'אנק שגלויים למשתמשים.

דברים פשוטים נוספים שאפשר לעשות בשלב מוקדם של הפעלת המכשיר:

  • מוודאים שהליבה כוללת את תיקון sched_blocked_reason של נקודת המעקב. נקודת המעקב הזו מופעלת באמצעות קטגוריית המעקב sched ב-systrace, ומספקת את הפונקציה שאחראית על השינה כשהשרשור הזה נכנס למצב שינה שלא ניתן להפריע לו. הוא חיוני לניתוח הביצועים, כי שינה ללא הפרעות היא אינדיקטור נפוץ מאוד לשינויים לא סדירים בזמן ההגעה של מנות נתונים.
  • מוודאים שיש לכם מספיק נתוני מעקב עבור צינורות הנתונים של ה-GPU והתצוגה. במערכות עדכניות על שבב (SOC) של Qualcomm, נקודות המעקב מופעלות באמצעות:
  • adb shell "echo 1 > /d/tracing/events/kgsl/enable"
    adb shell "echo 1 > /d/tracing/events/mdss/enable"
    

    האירועים האלה נשארים מופעלים כשמריצים את systrace, כדי שתוכלו לראות מידע נוסף על צינור העיבוד של התצוגה (MDSS) בקטע mdss_fb0. במערכות על שבב (SOC) של Qualcomm, לא מוצג מידע נוסף על ה-GPU בתצוגה הרגילה של systrace, אבל התוצאות מופיעות בנתוני המעקב עצמם (לפרטים, אפשר לעיין במאמר הסבר על systrace).

    מה שרוצים לקבל ממעקב כזה אחרי הצגת מודעות הוא אירוע יחיד שמציין באופן ישיר שמסגרת נמסרה לתצוגה. מכאן אפשר לקבוע אם הצלחתם להגיע לזמן רינדור פריים הרצוי. אם האירוע Xn מתרחש פחות מ-16.7 אלפיות השנייה אחרי האירוע Xn-1 (בהנחה שהתצוגה היא 60 Hz), סימן שלא היו בעיות בממשק (jank). אם ה-SOC שלכם לא מספק אותות כאלה, צריך לפנות לספק כדי לקבל אותם. קשה מאוד לנפות באגים של תנודות ללא אות ברור של השלמת פריים.

שימוש בבדיקות השוואה סינתטיות

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

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

נניח שיש שני SOC שמריצים את Benchmark X ומעבדים 1,000 פריימים של ממשק משתמש, ומדווחים על זמן העיבוד הכולל (ציון נמוך יותר הוא טוב יותר).

  • מערכת SOC 1 מעבדת כל פריים של Benchmark X ב-10 אלפיות השנייה ומקבלת ציון של 10,000.
  • מערכת SOC 2 מעבדת 99% מהפריימים ב-1 אלפית השנייה, אבל 1% מהפריימים ב-100 אלפיות השנייה, והציון שלה הוא 19,900 – ציון טוב בהרבה.

אם מדד ההשוואה מייצג את הביצועים בפועל של ממשק המשתמש, אי אפשר להשתמש ב-SOC 2. בהנחה שקצב הרענון הוא 60 הרץ, SOC 2 יציג פריים לא חלק כל 1.5 שניות של פעולה. בינתיים, מערכת SOC 1 (ה-SOC האיטי יותר לפי Benchmark X) תפעל בצורה חלקה לחלוטין.

שימוש בדוחות על באגים

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

שימוש ב-TouchLatency

כמה דוגמאות להתנהגות לא תקינה מגיעות מ-TouchLatency, שהוא עומס העבודה המועדף לשימוש תקופתי ב-Pixel וב-Pixel XL. הכלי זמין בכתובת frameworks/base/tests/TouchLatency ויש לו שני מצבים: זמן האחזור של המגע וכדור קופץ (כדי לעבור בין המצבים, לוחצים על הלחצן בפינה השמאלית העליונה).

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

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

הג'יטר לא תלוי בדרך כלל (אבל לא תמיד) במהירות השעון, ולכן כדאי להשתמש בבדיקה שפועלת במהירויות שעון נמוכות מאוד כדי לאבחן את הג'יטר, מהסיבות הבאות:

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