אופטימיזציה של זיכרון במשחקי Unreal ב-Android

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

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

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

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

אופטימיזציה של טעינת נכסים

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

  • הגדרה של קוד C++ והפרדת נתונים: הגדרה של פונקציות ליבה באמצעות UCLASS(Abstract) בקוד C++, והפרדה שלהן מהנתונים בפועל.
  • מצמצמים את ההורשה: מבצעים את הגדרת הנתונים ואת טעינת הנכסים רק בתוכניות Blueprint של צומתי עלים.
  • שימוש בהפניות עקיפות: כדי להימנע מהפניות קשיחות, כדאי להשתמש בהפניות עקיפות למאפיינים באמצעות TSoftObjectPtr, ולטעון נכסים באופן אסינכרוני רק כשצריך בזמן הריצה.
  • אמצעי זהירות: כשמשתמשים בטעינה אסינכרונית של נכסים, חשוב לוודא שבדיקות התוקף (IsValid) מתבצעות במהלך הקריאה החוזרת (callback) של סיום הטעינה, כדי למנוע מצבי מצבי הצבעה לא חוקיים או קריסות.

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

כוונון של מצביעים חכמים ומנגנון איסוף זבל (GC)

שימוש במצביעים חכמים (כמו TSharedPtr ו-TWeakPtr) במהלך פיתוח ב-C++‎ מונע מצביעים תלויים ודליפות זיכרון. בנוסף, השימוש ב-TWeakPtr מונע יצירה של הפניות חזקות לאובייקטים, כך שאיסוף האשפה לא מגדיל את מספר ההפניות כשמבצעים איסוף אובייקטים. כך נמנעת הישרדות מיותרת של אובייקטים, והמורכבות של גרף ההפניות ש-GC צריך לעבור בו פוחתת, מה שמקטין באופן משמעותי את התקורה של GC. אפשר לנהל את איסוף הג'יפה בצורה יעילה בסביבות ניידות על ידי הפעלת ForceGarbageCollection במהלך מעברים בין רמות או תקופות של חוסר פעילות, למשל כשאין אינטראקציה של המשתמש. בנוסף, הפעולה הזו יכולה להיות מופעלת כשהמשחק נמצא במצב שניתן לתפיסה או ברקע (לדוגמה, onStopcallbacks), וכך לוודא שמנגנון איסוף זבל מתבצע בלי להשפיע על חוויית המשחק הפעילה.

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

אופטימיזציה באמצעות דחיסה של טבלת מיקום מחדש

דחיסה של טבלת המיקום היא שיטת אופטימיזציה שדוחסת את הגודל של טבלת המיקום בקבצים בינאריים של ELF כדי להקטין את הגודל של קבצים בינאריים של Android. כש-minSDKVersion ב-Android הוא 23 ומעלה, יש תמיכה בדחיסת טבלת מיקום מחדש של APS. כש-minSDKVersion הוא 28 ומעלה, יש תמיכה נוספת בדחיסת טבלת מיקום מחדש של RELR, מה שמקטין באופן משמעותי את הגודל של קובץ ה-‎.so. הפעולה הזו לא רק מקטינה את גודל ההורדה, אלא גם מפחיתה את הזיכרון שבשימוש של mmap של ‎ .so בזמן הריצה, וכך מקלה על העומס הכולל על הזיכרון הפיזי.

AndroidToolChain.cs

if (MinSDKVersion >= 28)
{
  Result += " -Wl,--pack-dyn-relocs=android+relr,--use-android-relr-tags";
}
else if (MinSDKVersion >= 23)
{
  Result += " -Wl,--pack-dyn-relocs=android";
}

כדי להחיל את האופטימיזציה הזו, מוודאים שהאפשרות Enable compression of relocation tables (הפעלת דחיסה של טבלאות מיקום) בהגדרות הפרויקט או המשתנה bEnableAdvancedBinaryCompression מופעלת.

כדי לבדוק אם הדחיסה של RELR מוחלת על הקובץ הבינארי בצורה תקינה, משתמשים בכלי llvm-readelf מ-Android NDK.

llvm-readelf -d libUnreal.so | grep RELR

בפועל, בניית תבנית ThirdPerson של Unreal Engine ומדידה באמצעות dumpsys meminfo מאשרת שימוש מופחת ב-PSS וב-RSS עבור mmap של ‎ .so.

קטגוריה סה"כ PSS Private Dirty ניקוי פרטי החלפה של Dirty סה"כ RSS
‫.so mmap Before compression 170,210 24,360 141,564 0 242,204
‫.so mmap After compression 138,031 24,748 108,976 0 210,140

דחיסת מרקם

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

אופטימיזציה של סטרימינג של טקסטורות

בסביבות ניידות, שימוש ישיר בגודל מאגר הנתונים של סטרימינג הטקסטורה שמוגדר כברירת מחדל ב-Unreal Engine עלול להקצות מראש מאגר זיכרון גדול מדי ביחס למפרט המכשיר. לכן, מומלץ להגדיר פרופילים של מכשירים ניידים ולבחור גודל מתאים של מאגר לסטרימינג של טקסטורות בהתאם למגבלות הזיכרון של המכשיר. בנוסף, אפשר להשתמש בתכונה Statistic של הכלי Editor כדי לבדוק את נתוני הטקסטורה ולזהות טקסטורות שצורכות הרבה זיכרון ותופסות רזולוציות קיצוניות בזמן הריצה. אפשר לשלב את התכונה הזו עם התכונה Required Texture Resolution כדי לאכוף מגבלות או להקטין את הרזולוציות המקסימליות של טקסטורות שדורשות הרבה משאבים, וכך לשמור על שימוש מבוקר במשאבים בתוך מאגר הזיכרון שהוקצה לטקסטורות.