אופטימיזציה של הזיכרון היא חיונית כדי לספק חוויות משחק יציבות עם ביצועים גבוהים ב-Android. במדריך הזה מוסבר למה יעילות הזיכרון חשובה, איך מערכת ההפעלה Android מנהלת את מגבלות הזיכרון של התהליך, ואיך אפשר להשתמש במדדים החדשים של הזיכרון ב-Google Play Console כדי לעקוב אחרי האיכות הטכנית של המשחק ולשפר אותה.
החשיבות של אופטימיזציה של הזיכרון
אופטימיזציה של הזיכרון של המשחק חיונית כדי לשמר את השחקנים, להרחיב את התאימות למכשירים ולעמוד בתקני האיכות של הפלטפורמה:
- מניעת הפעלה במצב התחלתי (cold start) (חוויית שימוש ושימור משתמשים): כששחקן עובר באופן זמני מהמשחק (למשל, כדי לענות על התראה או לבדוק הודעה), מערכת ההפעלה מעבירה את תהליך המשחק לרקע. אם הזיכרון שבשימוש של זיכרון הרקע של המשחק גבוה מדי, המערכת נותנת עדיפות לסיום תהליך המשחק כדי לפנות RAM למשימות בחזית. בפעם הבאה שהמשתמש יחזור למשחק, במקום חזרה חלקה ומהירה למשחק, המשחק יצטרך לעבור הפעלה ארוכה של הפעלה במצב התחלתי (cold start) – טעינה מחדש מלאה של נכסי גרפיקה כבדים, אודיו וקבצים בינאריים של מנוע המשחק מהאחסון. שמירה על שימוש נמוך בזיכרון ברקע מונעת את הסגירות השקטות האלה של הרקע, שומרת על מצב המשתמש ומבטיחה שהשחקנים יוכלו להמשיך את הסשן שלהם באופן מיידי. לפרטים נוספים על ההתנהגות של LMK במערכת, אפשר לעיין במדריך Android Vitals - Low memory killers.
- יציבות המערכת והמכשיר: שימוש לא יעיל בזיכרון ודליפות זיכרון פוגעים בבריאות הכללית של המערכת. כשזיכרון המערכת מוגבל, המערכת נתונה ללחץ רב, שגורם לירידה בקצב הפריימים, לגמגום בממשק המשתמש ולשיבושים באודיו. אם העומס על הזיכרון חמור מדי, מנגנון ה-LMK (הפסקת תהליכים בגלל מחסור בזיכרון) של המערכת מפסיק באופן אגרסיבי תהליכים ברקע, וכתוצאה מכך יישומים אחרים נאלצים להתמודד עם הפעלה איטית של התהליכים ועם אובדן של מצב המשתמש כשמפעילים עוברים בין משימות.
- סיום תהליכים ברמת הפלטפורמה: החל מ-Android 17 (רמת API 37), המערכת יוזמת יותר פעולות לסיום תהליכים שצורכים יותר מדי זיכרון. אם טביעת הרגל של המשחק גדולה מדי, מערכת ההפעלה יכולה להפסיק את התהליך שלו בפתאומיות בלי ליצור דוח קריסות רגיל.
- תאימות המכשיר: למרות שבמכשירי הדגל יש RAM בנפח 12GB עד 16GB, חלק גדול מקהל הגיימרים העולמי משתמש במכשירים עם RAM בנפח 4GB או 6GB. ניהול נכון של הזיכרון מבטיח שהמשחק יישאר נגיש ויגיב בכל רמות החומרה, בלי שיהיה צורך בחבילות נכסים מורכבות ונפרדות.
הסבר על הזיכרון ב-Android
כדי לתכנן אסטרטגיות יעילות לניהול תקציב הזיכרון, המפתחים צריכים להבין איך פלטפורמת Android מנהלת את הזיכרון הפיזי ואיך היא מודדת את טביעת הרגל הפעילה של המשחק.
מושגים מרכזיים בזיכרון של Android
למידע על מושגי יסוד בנושא ניהול זיכרון ברמת הפלטפורמה, אפשר לעיין במסמכי התיעוד הרשמיים בנושא סקירה כללית על ניהול זיכרון. במשאב הזה מוסבר על ארבעה תחומים בארכיטקטורה:
- סקירה כללית של הזיכרון: מערכת Android משתמשת בהחלפה (paging) ובמיפוי זיכרון (mmap) כדי לנהל את ה-RAM. הוא לא תומך בקובץ החלפה מסורתי בדיסק, אלא מסתמך על דחיסת דפים (באמצעות zRAM) ועל שחזור דפים כדי לפנות זיכרון פיזי.
- הקצאת זיכרון בין תהליכים: מערכת Android משתפת את ה-RAM בכל המערכת. הוא מקצה ערימות ספציפיות להרצה של מכונה וירטואלית של Dalvik או ART, ומאפשר לסביבות פיתוח מקומיות (כמו מנועי משחקים של C++) לבקש זיכרון מהערימה המקומית של המערכת.
- ניהול הזיכרון של האפליקציה: מערכת Android פועלת לפי מודל מרובה תהליכים, ולכן היא מצפה מהאפליקציות לעקוב באופן דינמי אחרי מצב מחזור החיים שלהן ולשחרר מרצונן משאבים מיותרים (כמו גרפיקה ומפות סיביות שלא נשמרו במטמון) כדי לשמור על תקינות המערכת.
- סקירה כללית של תהליכים ושרשורים: המערכת מסווגת תהליכים להיררכיה על סמך החשיבות שלהם והנראות שלהם למשתמשים, וקובעת אילו תהליכים יישארו פעילים ואילו יסתיימו ראשונים בתנאים של זיכרון נמוך.
מדד הזיכרון הכולל שבשימוש
ב-Android 17, תהליך ההערכה של צריכת הזיכרון ברמת הפלטפורמה מתבצע באמצעות 'טביעת רגל כוללת של זיכרון' ולא באמצעות 'גודל תושב כולל' (RSS) או 'גודל זיכרון וירטואלי'.
טביעת רגל כוללת בזיכרון = RSS אנונימי (RssAnon) + החלפה לא דחוסה (VmSwap)
כדי למנוע חריגה של משחקים מהמגבלות של הפלטפורמה, המפתחים צריכים להבין בדיוק מה המדדים האלה מייצגים ברמת המערכת. מידע נוסף על המדדים האלה, על הקצאות של זיכרון RAM פיזי ועל אופן הטיפול בדפים שמגובים בקובץ זמין במאמר הסבר על מדדי RSS ו-Swap במדריך מעקב אחר השימוש בזיכרון.
מגבלות זיכרון
כדי לשמור על יציבות המערכת ולוודא שאפליקציות לא צורכות משאבים מוגזמים, פלטפורמת Android מנהלת את מגבלות הזיכרון של תהליכים שפועלים.
הגבלת הזיכרון ב-Android מגרסה 17 ואילך
ב-Android 17 (רמת API 37) ואילך, יש ניהול של מגבלות זיכרון מחמירות לכל אפליקציה באמצעות Linux cgroup v2, כדי למנוע מאפליקציות ספציפיות לגרום לחוסר יציבות במערכת. פרטים נוספים על ההטמעה הטכנית זמינים במדריך בנושא הגבלת הזיכרון ב-AOSP ובמאמר Prioritizing Memory Efficiency: Essential Steps for Android 17 Blog.
- מנגנון: הכלי Memory Limiter עוקב אחרי כל תהליכי האפליקציה ומקצה מגבלות באופן דינמי על סמך מצב מחזור החיים של התהליך:
- תהליכים גלויים (חזיתיים): תהליכי אפליקציה שמציגים כרגע ממשק משתמש צפויים להפעיל קבוצת עבודה גדולה יותר של משאבים, והם מקבלים מגבלה נדיבה יותר.
- תהליכים לא גלויים (ברקע או בשירותים): תהליכים של אפליקציות שמבצעים פעולות פעילות בלי להציג ממשק משתמש מוגבלים לתקציב מצומצם יותר.
- מאפייני ליבה: השירות מסתמך על שני מאפיינים עיקריים:
-
memory.high: מגבלה רכה. אם חורגים מהערך הזה, ליבת המערכת מגבילה את התהליך ומנסה לשחרר זיכרון באופן אגרסיבי. החזרת הזיכרון יכולה לגרום לירידה בביצועים של המשחק. -
memory.swap.max: ניהול של מגבלה קשיחה על נפח הזיכרון הווירטואלי או נפח ה-zRAM שהתהליך יכול להשתמש בהם.
-
- התנהגות של סיום התהליך: אם תהליך ממשיך להקצות זיכרון אנונימי מעבר ל-
memory.highוממצה את קיבולת ההחלפה שלו, ההקצאות נכשלות ומערכת ההפעלה מסיימת את התהליך באופן שקט. הסיום הזה מתועד באמצעותApplicationExitInfoבקטע Memory Limiter exit reason (זמין החל מ-Android 17, 26Q4).
מעקב אחרי השימוש בזיכרון
כדי לבצע אופטימיזציה יעילה של הזיכרון של המשחק, קודם צריך להבין איך פלטפורמת Android מודדת את טביעת הרגל שלו. ב-Android 17, מדד הזיכרון מתעדכן כדי לעקוב אחרי הסכום של RSS אנונימי (RssAnon) ושל Swap לא דחוס (VmSwap), לא כולל זיכרון מגובה בקובץ או זיכרון פרטי של GPU. במדריך הזה מוסבר איך להשתמש בכלים ברמת המערכת כמו Perfetto ו-meminfo, איך להטמיע ממשקי API לאבחון כמו ProfilingManager ו-onTrimMemory, ואיך לחלץ הקצאות מדויקות של זיכרון ב-Unity וב-Unreal Engine. הסבר על יצירת פרופיל מדויק של המשחק כדי להימנע מבעיות בביצועים שקשורות לסקר זיכרון בזמן ריצה (runtime) בשיטה המסורתית.
מידע נוסף מופיע במאמר בנושא מעקב אחרי השימוש בזיכרון.
אסטרטגיות להפחתת הזיכרון
מנועי משחקים מפשטים את הפיתוח בפלטפורמות שונות, אבל הטיפול בזיכרון שמוגדר בהם כברירת מחדל יכול להפעיל מגבלות זיכרון ברמת מערכת ההפעלה. בדף הזה מפורטים שלבים מעשיים לאופטימיזציה שמותאמים במיוחד ל-Unity ול-Unreal Engine. הסבר למה הסתמכות על onTrimMemory שמבוסס על Java עלולה לגרום לקיפאון ב-Unity, ואיך להשתמש במקום זאת בפונקציות קריאה חוזרת (callback) של מחזור החיים המקורי. בנוסף, תמצאו מידע על אופטימיזציות חשובות ברמת הנכס, כמו שימוש בדחיסת טקסטורה מסוג ASTC 8x8 והגדרת הסרת נכסים, כדי שהמשחק יפעל בצורה חלקה בכל רמות החומרה.
מידע נוסף מופיע במאמר בנושא צמצום השימוש בזיכרון.