איך בודקים את השימוש בזיכרון באמצעות כלי Unity

במערכת Android, סף מגבלת הזיכרון של Memory Limiter (Android 17 ואילך) וערך ההשוואה של נתוני תפקוד האפליקציה שקשורים לזיכרון ב-Google Play Console הוא anonymous RSS + swap. הערך הזה מייצג את רמת העומס על הזיכרון הפיזי שתהליך מסוים תופס בפועל, והוא מדד קריטי שצריך לעקוב אחריו כדי שהאפליקציה תפעל בצורה יציבה.

בשלב הזה, קשה למדוד את השימוש בזיכרון שתואם בדיוק ל-'anonymous RSS + swap' בזמן הריצה או באופן סטטי בתוך Unity Engine.

כדי לפתור את הבעיה הזו, בדף הזה מוסבר איך לעקוב אחרי הערך anonymous RSS + swap ברמת מהימנות גבוהה באמצעות קירוב בעזרת Unity Profiler,‏ ProfileRecorder API ו-Unity Memory Profiler בסביבת Unity Engine.

בדיקת השימוש בזיכרון בזמן הריצה

ב-Unity, יכול להיות מאתגר למדוד ישירות את השימוש בזיכרון של Android בזמן ריצה, בלי תקורה, בפורמט המדויק של RSS אנונימי + החלפה. עם זאת, אפשר להעריך את הערך הזה (בטווח של 10% שונות) באמצעות ממשקי ה-API המובנים של Unity Profiler.

שיטה 1: שימוש בProfiler

כדי להעריך את השימוש הכולל בזיכרון, אפשר לסכם את הערכים שמוחזרים על ידי ממשקי ה-API הבאים של Profiler:

using UnityEngine;
using UnityEngine.Profiling;

public class MemoryMonitor : MonoBehaviour
{
    public long GetEstimatedMemoryUsageBytes()
    {
        long totalReserved = Profiler.GetTotalReservedMemoryLong();
        long monoHeapSize = Profiler.GetMonoHeapSizeLong();

        return totalReserved + monoHeapSize;
    }
}

שיטה 2: שימוש במחלקה ProfilerRecorder

אפשר לעקוב אחרי המדדים Total Reserved Memory ו-Gfx Reserved Memory באמצעות ProfilerRecorder:

  • בגרסאות פיתוח: מחשבים את הערך של 'Total Reserved Memory' (הזיכרון הכולל ששמור) פחות הערך של 'Gfx Reserved Memory' (הזיכרון ששמור ל-Gfx).

  • בגרסאות Release: משתמשים ישירות ב-Total Reserved Memory.

using Unity.Profiling;
using UnityEngine;

public class MemoryMonitor : MonoBehaviour
{
    ProfilerRecorder totalRecorder;

#if DEVELOPMENT_BUILD
    ProfilerRecorder gfxRecorder;
#endif

    private void OnEnable()
    {
        totalRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "Total Reserved Memory");

#if DEVELOPMENT_BUILD
        gfxRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "Gfx Reserved Memory");
#endif
    }

    private void OnDisable()
    {
        totalRecorder.Dispose();

#if DEVELOPMENT_BUILD
        gfxRecorder.Dispose();
#endif
    }

    public long GetEstimatedMemoryUsageBytes()
    {
#if DEVELOPMENT_BUILD
        if (totalRecorder.Valid && gfxRecorder.Valid)
        {
            return totalRecorder.LastValue - gfxRecorder.LastValue;
        }
#else
        if (totalRecorder.Valid)
        {
            return totalRecorder.LastValue;
        }
#endif
        return 0;
    }
}

מגבלות:

  • אי אפשר לעקוב אחרי הקצאות זיכרון שעוקפות את מנהל הזיכרון של Unity (למשל, NativeMalloc(IntPtr size)) באמצעות ממשקי ה-API האלה.

  • יכול להיות שממשקי ה-API האלה לא נתמכים, בהתאם לגרסת Unity שלכם.

ניתוח של פירוט הזיכרון (ניתוח סטטי)

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

Unity Memory Profiler

כדי לבצע ניתוח מעמיק של הזיכרון ולבצע אופטימיזציה של המשחק, אפשר להשתמש בחבילה Unity Memory Profiler. כדי לאתר את הפריטים שמתאימים לשימוש ב-Android OS ‏anonymous RSS + swap:

  1. יצירת תמונת מצב באמצעות Unity Memory Profiler.
  2. פותחים את התמונה של מצב המערכת ועוברים אל All of Memory > Resident Memory on Device (כל הזיכרון > זיכרון תושב במכשיר).
  3. חשבו את הסכום של הפריטים הבאים:

    • לא נכלל במעקב
    • סביבת זמן ריצה ל-Android‏ (ART)
    • פורמט מותאם
    • מנוהל
    מדדי זיכרון שניתן לראות כשמגדירים Resident Memory בכלי Unity Memory Profiler
    איור 1. תיאורי הזיכרון מאוחסנים בזיכרון התושב

הסכום הכולל משמש כתחליף מעשי ל-anonymous RSS + swap במערכת ההפעלה Android, ומספק תובנות פרקטיות לניתוח הזיכרון של המשחק.

מגבלה:

  • במצבים של עומס רב על הזיכרון, מערכת ההפעלה Android עשויה לדחוס או להחליף דפי זיכרון פעילים כדי לפנות זיכרון RAM. מכיוון ש-Unity Memory Profiler לא יכול לעקוב אחרי זיכרון שהועבר, יכול להיות שיהיו אי-התאמות בתרחישים של שימוש בזיכרון גבוה.

  • יכול להיות שהתכונות האלה יהיו מוגבלות בהתאם לגרסת Unity שבה אתם משתמשים.

דוגמאות לסקריפטים של C# ‎

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

  • בסקריפט AndroidProcessStats מודגם איך להשתמש ב-JNI ‏ (Java Native Interface) כדי לקשר לממשקי ה-API של Android ActivityManager, והוא מספק פונקציונליות לאחזור תהליכים שפועלים, החשיבות שלהם והמצב ברמת מערכת ההפעלה. הוא כולל גם שיטות קלות משקל לקריאת נתוני זיכרון ישירות ממערכת הקבצים של Linux /proc.
  • סקריפט GameMemoryMonitor מראה איך לחבר את המידע הזה לניטור וניפוי באגים רציפים, כולל איך לבצע סקר של ממשקי ה-API של Android בשרשור ברקע כדי למנוע תקורה של ביצועים (כי שימוש ב-JNI וביצוע סקר של מידע על הזיכרון עלולים להיות יקרים), ואיך להתחבר לאירועי מחזור חיים כמו OnApplicationPause כדי לעקוב אחרי שינויים בזיכרון כשהאפליקציה פועלת ברקע או מופעלת מחדש.

כדי להשתמש בכלים האלה, מוסיפים את שני הסקריפטים לתיקיית הנכסים ומצרפים את הסקריפט GameMemoryMonitor ל-GameObject בסצנה.

הערכה מדויקת יותר

כדי לאמת את הערכים המדויקים של מערכת ההפעלה Android ‏anonymous RSS + swap, צריך להשתמש ב-Perfetto. הכלי מאפשר למדוד את השימוש המדויק בזיכרון ברמת המערכת ולנתח ביסודיות את הזיכרון שבשימוש של המשחק.

ממשק המשתמש של Perfetto שמציג RSS אנונימי, החלפה וסכום השאילתה שלהם.
איור 2. ממשק המשתמש של Perfetto שבו מוצגים RSS אנונימי, החלפה וסכום השאילתה שלהם.