מידע על ניהול זיכרון

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

החשיבות של אופטימיזציה של הזיכרון

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

  • מניעת הפעלה במצב התחלתי (cold start) (חוויית שימוש ושימור משתמשים): כששחקן עובר באופן זמני מהמשחק (למשל, כדי לענות על התראה או לבדוק הודעה), מערכת ההפעלה מעבירה את תהליך המשחק לרקע. אם הזיכרון שבשימוש ברקע של המשחק גבוה מדי, מערכת Low Memory Killer ‏ (LMK) נותנת עדיפות לסיום תהליך המשחק כדי לפנות 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: ניהול מגבלה קשיחה על נפח ה-swap או ה-zRAM שהתהליך יכול להשתמש בו.
  • התנהגות בסיום: אם תהליך ממשיך להקצות זיכרון אנונימי מעבר ל-memory.high וממצה את קיבולת ההחלפה שלו, ההקצאות נכשלות ומערכת ההפעלה מסיימת את התהליך באופן שקט. הסיום הזה מתועד באמצעות ApplicationExitInfo בקטע Memory Limiter exit reason (סיבת היציאה של Memory Limiter) (זמין החל מ-Android 17, ‏ 26Q4).

מגבלות חדשות על הזיכרון בתכונה 'תפקוד האפליקציה' ב-Play Console

כדי לעזור למפתחים לזהות בעיות בזיכרון באופן יזום, אנחנו משיקים ב-Google Play מדדים חדשים ב-Android Vitals ב-Play Console. מערכת Play Console עוקבת אחרי האחוזון ה-90 (P90) של RSS אנונימי + הזיכרון שבשימוש של זיכרון החלפה של סשנים במשחק כדי לזהות חריגים קיצוניים.

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

הנחיות מפורטות ומגבלות זיכרון זמינות במאמר תפקוד האפליקציה – מהם ספי ההתנהגות הלא תקינה?.

שירותים שהשפיעו על המשתמשים

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

  • שירותים שפועלים בחזית (FGS)
  • משרות דחופות
  • משימות של העברת נתונים ביוזמת המשתמשים
  • שירותים שקשורים למערכת או שקשורים לאפליקציות אחרות

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

דרישות לשימוש ב-R8

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

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

דרישות לגבי מפות סיביות

מפות סיביות מייצגות חלק גדול מאוד מהשימוש בזיכרון במשחקים מודרניים באיכות גבוהה. מכיוון שנתוני פיקסלים של Bitmap מאוחסנים ישירות ב-heap המקורי הלא מנוהל ב-Android 8.0 (רמת API‏ 26) ואילך, טעינת תמונות לא ממוטבת עלולה לגרום לתהליכים לחרוג מספי הזיכרון של הפלטפורמה. שיטות מומלצות לשימוש בתמונות

מעקב אחרי השימוש בזיכרון

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

מידע נוסף מופיע במאמר בנושא צמצום השימוש בזיכרון.