הגדרת מעקב המערכת

ניתן להגדיר מעקב מערכת כדי לתעד ה-CPU ופרופיל ה-thread של האפליקציה בפרק זמן קצר. לאחר מכן אפשר להשתמש בדוח הפלט ממעקב המערכת כדי לשפר את או של ביצועים.

הגדרת מעקב מערכת מבוסס-משחקים

הכלי Systrace זמין בשתי דרכים:

Systrace הוא כלי ברמה נמוכה שמבצע את הפעולות הבאות:

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

הגדרות אופטימליות

חשוב לתת לכלי סדרה סבירה של טענות:

  • קטגוריות: קבוצת הקטגוריות הטובה ביותר להפעלה במערכת מבוססת-משחקים נתוני המעקב הם: {sched, freq, idle, am, wm, gfx, view, sync, binder_driver, hal, dalvik}.
  • גודל מאגר נתונים זמני: כלל אצבע הוא גודל של 10MB לכל ליבת מעבד (CPU) מאפשר מעקב שאורכו כ-20 שניות. לדוגמה, אם במכשיר יש שני מעבדים עם ארבע ליבות (8 ליבות בסך הכול), ערך מתאים שיועבר תוכנית systrace היא 80,000KB (80MB).

    אם המשחק שלכם מבצע הרבה שינויים בהקשר, להגדיל את מאגר הנתונים הזמני ל-15MB לכל ליבת מעבד (CPU).

  • אירועים מותאמים אישית: אם אתם מגדירים אירועים מותאמים אישית אירועים שיתעדו במשחק, הפעילו הדגל -a, שמאפשר ל-Systrace לכלול את האירועים המותאמים אישית דוח פלט.

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

python systrace.py -a com.example.myapp -b 80000 -o my_systrace_report.html \
  sched freq idle am wm gfx view sync binder_driver hal dalvik

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

  1. מפעילים את האפשרות מעקב אחר אפליקציות שניתנות לניפוי באגים.

    שפת תרגום להשתמש בהגדרה זו, המכשיר צריך להיות בגודל של 256MB או 512MB זמינים (בהתאם אם למעבד יש 4 או 8 ליבות), וכל פיסת זיכרון של 64MB יהיה זמין כמקטע רציף.

  2. בוחרים באפשרות קטגוריות ואז מפעילים את הקטגוריות ברשימה הבאה:

    • am: מנהל הפעילות
    • binder_driver: מנהל התקן Binder Kernel
    • dalvik: VM של Dalvik
    • freq: תדירות המעבד (CPU)
    • gfx: גרפיקה
    • hal: מודולים של חומרה
    • idle: המעבד לא פעיל (CPU)
    • sched: תזמון מעבד (CPU)
    • sync: סנכרון
    • view: צפייה במערכת
    • wm: מנהל החלונות
  3. הפעל את האפשרות מעקב אחר תיעוד.

  4. טוענים את המשחק.

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

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

אספת את הנתונים הסטטיסטיים של הביצועים שנדרשים לצורך ניתוח נוסף של הבעיה.

כדי לחסוך מקום בכונן, מתבצע מעקב אחרי המערכת במכשיר ושומרים קבצים במעקב דחוס פורמט (*.ctrace). כדי לבטל את הדחיסה של הקובץ הזה במהלך יצירת דוח, צריך להשתמש באופרטור של שורת הפקודה וכוללים את האפשרות --from-file:

python systrace.py --from-file=/data/local/traces/my_game_trace.ctrace \
  -o my_systrace_report.html

שיפור תחומים ספציפיים של ביצועים

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

מהירות טעינה

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

  • ביצוע טעינה מדורגת. אם אתם משתמשים באותם נכסים של סצנות או שלבים במשחק, צריך לטעון את הנכסים האלה רק פעם אחת.
  • הקטין את גודל הנכסים. כך אפשר לקבץ תמונות במצב לא דחוס של הנכסים האלה ב-APK של המשחק.
  • שימוש בשיטת דחיסה חסכונית בדיסק. דוגמה לשיטה כזו zlib.
  • שימוש ב-IL2CPP במקום מונו. (רלוונטי רק אם משתמשים ב-Unity). IL2CPP מספק ביצועים טובים יותר את ביצועי הביצוע של סקריפטים של C# .
  • הופכים את המשחק לכמה שרשורים. לפרטים נוספים אפשר לעיין בקצב הפריימים עקביות.

עקביות של קצב פריימים

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

ריבוי שרשורים

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

ה-Systrace שמוצג באיור 1 מציג התנהגות שאופיינית למשחק משתמש במעבד אחד בלבד בכל פעם:

תרשים שרשורים
בנתוני מעקב של המערכת

איור 1. דוח Systrace של משחק עם שרשור יחיד

כדי לשפר את ביצועי המשחק, כדאי להפוך את המשחק לכמה שרשורים. בדרך כלל, המודל הכי טוב הוא לנהל 2 שרשורים:

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

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

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

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

אחרי החלת השינויים האלה, המשחק אמור להשתמש ב-2 מעבדים (CPU) לפחות בו-זמנית, כפי שמוצג באיור 2:

תרשים שרשורים
בנתוני מעקב של המערכת

איור 2. דוח Systrace למשחק עם שרשורים מרובים

הרכיב בממשק המשתמש בטעינה

תרשים מסגרת
  ערימה בתוך מעקב מערכת
איור 3. דוח Systrace של משחק שמבצע רינדור של עשרות ממשקים רכיבים באותו זמן

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

דוח Systrace שמוצג באיור 3 הוא דוגמה למסגרת של ממשק משתמש לנסות לעבד יותר מדי רכיבים ביחס יכולות.

כדאי לצמצם את זמן העדכון של ממשק המשתמש ל-2-3 אלפיות השנייה. אפשר להשיג עדכונים מהירים כאלה על ידי ביצוע אופטימיזציה דומה לזו:

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

צריכת חשמל

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

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

ניהול שרשורים עם הרבה זיכרון במעבד אחד

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

דחיית עבודה שנמשכת זמן קצר למעבדים (CPU) עם מתח נמוך יותר

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

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

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

תרשים שרשורים
בנתוני מעקב של המערכת

איור 4. דוח Systrace שמציג הקצאה לא אופטימלית של שרשורים את המעבדים (CPU) של המכשיר

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

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

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

#include <sched.h>
#include <sys/types.h>
#include <unistd.h>

pid_t my_pid; // PID of the process containing your thread.

// Assumes that cpu0, cpu1, cpu2, and cpu3 are the "slow CPUs".
cpu_set_t my_cpu_set;
CPU_ZERO(&my_cpu_set);
CPU_SET(0, &my_cpu_set);
CPU_SET(1, &my_cpu_set);
CPU_SET(2, &my_cpu_set);
CPU_SET(3, &my_cpu_set);
sched_setaffinity(my_pid, sizeof(cpu_set_t), &my_cpu_set);

עומס תרמי

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

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

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

Kotlin

class MainActivity : AppCompatActivity() {
    lateinit var powerManager: PowerManager

    override fun onCreate(savedInstanceState: Bundle?) {
        powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
        powerManager.addThermalStatusListener(thermalListener)
    }
}

Java

public class MainActivity extends AppCompatActivity {
    PowerManager powerManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        powerManager.addThermalStatusListener(thermalListener);
    }
}

הגדרת הפעולות לביצוע כשהמאזין מזהה סטטוס שינוי. אם במשחק שלך נעשה שימוש ב-C/C++, יש להוסיף קוד לרמות הסטטוס התרמיות onThermalStatusChanged() כדי לקרוא לקוד המשחק המקורי באמצעות JNI או להשתמש ב-Thermal API המקורי.

Kotlin

val thermalListener = object : PowerManager.OnThermalStatusChangedListener() {
    override fun onThermalStatusChanged(status: Int) {
        when (status) {
            PowerManager.THERMAL_STATUS_NONE -> {
                // No thermal status, so no action necessary
            }

            PowerManager.THERMAL_STATUS_LIGHT -> {
                // Add code to handle light thermal increase
            }

            PowerManager.THERMAL_STATUS_MODERATE -> {
                // Add code to handle moderate thermal increase
            }

            PowerManager.THERMAL_STATUS_SEVERE -> {
                // Add code to handle severe thermal increase
            }

            PowerManager.THERMAL_STATUS_CRITICAL -> {
                // Add code to handle critical thermal increase
            }

            PowerManager.THERMAL_STATUS_EMERGENCY -> {
                // Add code to handle emergency thermal increase
            }

            PowerManager.THERMAL_STATUS_SHUTDOWN -> {
                // Add code to handle immediate shutdown
            }
        }
    }
}

Java

PowerManager.OnThermalStatusChangedListener thermalListener =
    new PowerManager.OnThermalStatusChangedListener () {

    @Override
    public void onThermalStatusChanged(int status) {

        switch (status)
        {
            case PowerManager.THERMAL_STATUS_NONE:
                // No thermal status, so no action necessary
                break;

            case PowerManager.THERMAL_STATUS_LIGHT:
                // Add code to handle light thermal increase
                break;

            case PowerManager.THERMAL_STATUS_MODERATE:
                // Add code to handle moderate thermal increase
                break;

            case PowerManager.THERMAL_STATUS_SEVERE:
                // Add code to handle severe thermal increase
                break;

            case PowerManager.THERMAL_STATUS_CRITICAL:
                // Add code to handle critical thermal increase
                break;

            case PowerManager.THERMAL_STATUS_EMERGENCY:
                // Add code to handle emergency thermal increase
                break;

            case PowerManager.THERMAL_STATUS_SHUTDOWN:
                // Add code to handle immediate shutdown
                break;
        }
    }
};

זמן אחזור מסוג 'מגע לתצוגה'

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

כדי לבדוק אם אפשר לשפר את קצב הפריימים במשחק, צריך להשלים את את השלבים הבאים:

  1. יצירת דוח Systrace שכולל את הקטגוריות gfx ו-input. קטגוריות אלה כוללות מדידות שימושיות במיוחד לקביעת זמן אחזור של נגיעה במסך.
  2. צריך לעיין בקטע SurfaceView בדוח Systrace. מאגר נתונים זמני בנפח גדול מדי גורמת לכך שמספר המשיכה של מאגר הנתונים הזמני הממתין לתנודה בין 1 ל-2, כפי שמוצג באיור 5:

    תרשים של
תור של מאגר נתונים זמני בתוך מעקב מערכת

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

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

משלבים את Android Frame Pacing API במשחק

Android Frame Pacing API עוזר לך לבצע החלפות בין פריימים ולהגדיר מרווח החלפה כך שהמשחק שומר על קצב פריימים עקבי יותר.

הפחתת הרזולוציה של הנכסים שאינם ממשק משתמש

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

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

עיבוד חלק

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

תרשים מסגרות
חסר חלון Vsync כי הוא התחיל לשרטט מאוחר מדי

איור 6. דוח Systrace שמראה איך מסגרת עלולה לפספס Vsync

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

כדי לטפל במצב הזה, צריך להשתמש בקצב הפריימים ב-Android API, שתמיד מציג מסגרת חדשה חזית הגל של VSync.

מצב זיכרון

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

במצב הזה, אפשר לבדוק את הפעילות של המעבד (CPU) בדוח Systrace ולראות באיזו תדירות המערכת מבצעת קריאות לדימון (daemon) של kswapd. אם מתבצעות הרבה שיחות במהלך הפעלת המשחק, מומלץ לבדוק מקרוב הוא ניהול וניקוי זיכרון.

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

מצב השרשור

כשמנווטים בין הרכיבים האופייניים בדוח Systrace, אפשר לצפות את משך הזמן שבמהלך שרשור מסוים בכל שרשור אפשרי על ידי בחירה השרשור בדוח, כפי שמוצג באיור 7:

תרשים של
דוח Systrace

איור 7. דוח Systrace שמראה איך בחירת שרשור גורמת דוח להצגת סיכום מצב של השרשור הזה

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

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

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

מידע נוסף על שיפור ביצועי המשחק זמין במאמרים הבאים משאבים נוספים:

סרטונים

  • מצגת של Systrace for Games מכנס מפתחי המשחקים ל-Android לשנת 2018