اشکال زدایی LMK ها

حل LMK ها در بازی Unity شما یک فرآیند سیستماتیک است:

شکل ۱. مراحل حل مشکل Low Memory Kills (LMK) در بازی‌های Unity.

دریافت تصویر لحظه‌ای از حافظه

از Unity Profiler برای گرفتن یک snapshot از حافظه مدیریت‌شده توسط Unity استفاده کنید. شکل 2 لایه‌های مدیریت حافظه‌ای را که Unity برای مدیریت حافظه در بازی شما استفاده می‌کند، نشان می‌دهد.

شکل ۲. نمای کلی مدیریت حافظه در یونیتی.

حافظه مدیریت‌شده

مدیریت حافظه یونیتی یک لایه حافظه کنترل‌شده را پیاده‌سازی می‌کند که از یک هیپ مدیریت‌شده و یک جمع‌آوری‌کننده زباله برای تخصیص و تخصیص خودکار حافظه استفاده می‌کند. سیستم حافظه مدیریت‌شده یک محیط اسکریپت‌نویسی C# مبتنی بر Mono یا IL2CPP است. مزیت سیستم حافظه مدیریت‌شده این است که از یک جمع‌آوری‌کننده زباله برای آزادسازی خودکار تخصیص‌های حافظه استفاده می‌کند.

حافظه مدیریت نشده در سی شارپ

لایه حافظه مدیریت نشده C# دسترسی به لایه حافظه بومی را فراهم می‌کند و امکان کنترل دقیق بر تخصیص حافظه را هنگام استفاده از کد C# فراهم می‌کند. این لایه مدیریت حافظه از طریق فضای نام Unity.Collections و توسط توابعی مانند UnsafeUtility.Malloc و UnsafeUtility.Free قابل دسترسی است.

حافظه بومی

هسته داخلی C/C++ یونیتی از یک سیستم حافظه بومی برای مدیریت صحنه‌ها، دارایی‌ها، APIهای گرافیکی، درایورها، زیرسیستم‌ها و بافرهای افزونه استفاده می‌کند. در حالی که دسترسی مستقیم محدود است، می‌توانید با خیال راحت داده‌ها را با API C# یونیتی دستکاری کنید و از کد بومی کارآمد بهره‌مند شوید. حافظه بومی به ندرت نیاز به تعامل مستقیم دارد، اما می‌توانید با استفاده از Profiler تأثیر حافظه بومی بر عملکرد را رصد کنید و تنظیمات را برای بهینه‌سازی عملکرد تنظیم کنید.

همانطور که در شکل 3 نشان داده شده است، حافظه بین C# و کد بومی مشترک نیست. داده‌های مورد نیاز C# هر بار که مورد نیاز باشد، در فضای حافظه مدیریت‌شده تخصیص داده می‌شوند.

برای مثال، برای اینکه کد بازی مدیریت‌شده (C#) به داده‌های حافظه بومی موتور دسترسی پیدا کند، فراخوانی GameObject.transform یک فراخوانی بومی برای دسترسی به داده‌های حافظه در ناحیه بومی ایجاد می‌کند و سپس مقادیر را با استفاده از Bindings به C# برمی‌گرداند. Bindings قراردادهای فراخوانی مناسب را برای هر پلتفرم تضمین می‌کند و به طور خودکار انواع مدیریت‌شده را به معادل‌های بومی آنها هدایت می‌کند.

این اتفاق فقط برای اولین بار رخ می‌دهد، زیرا پوسته مدیریت‌شده برای دسترسی به ویژگی transform در کد native حفظ می‌شود. ذخیره کردن ویژگی transform می‌تواند تعداد تماس‌های رفت و برگشتی بین کد managed و native را کاهش دهد، اما مفید بودن ذخیره کردن به تعداد دفعات استفاده از ویژگی بستگی دارد. همچنین، توجه داشته باشید که Unity هنگام دسترسی به این APIها، بخش‌هایی از حافظه native را در حافظه managed کپی نمی‌کند.

شکل ۳. دسترسی به حافظه بومی از طریق کد مدیریت‌شده‌ی سی‌شارپ

برای کسب اطلاعات بیشتر، به مقدمه‌ای بر حافظه در یونیتی مراجعه کنید.

علاوه بر این، تعیین بودجه حافظه برای اجرای روان بازی شما بسیار مهم است و پیاده‌سازی یک سیستم تجزیه و تحلیل یا گزارش‌دهی مصرف حافظه تضمین می‌کند که هر نسخه جدید از بودجه حافظه تجاوز نکند. ادغام تست‌های حالت بازی با ادغام مداوم (CI) برای تأیید مصرف حافظه در مناطق خاص بازی، استراتژی دیگری برای کسب بینش بهتر است.

مدیریت دارایی‌ها

این بخش، تأثیرگذارترین و کاربردی‌ترین بخش مصرف حافظه است. در اسرع وقت آن را ثبت کنید.

میزان استفاده از حافظه در بازی‌های اندروید می‌تواند بسته به نوع بازی، تعداد و نوع دارایی‌ها و استراتژی‌های بهینه‌سازی حافظه، به‌طور قابل‌توجهی متفاوت باشد. با این حال، عوامل مؤثر در استفاده از حافظه معمولاً شامل بافت‌ها، مش‌ها، فایل‌های صوتی، سایه‌زن‌ها، انیمیشن‌ها و اسکریپت‌ها هستند.

تشخیص دارایی‌های تکراری

اولین قدم، شناسایی دارایی‌های با پیکربندی ضعیف و دارایی‌های تکراری با استفاده از پروفایلر حافظه، یک ابزار گزارش ساخت یا حسابرس پروژه است.

بافت‌ها

پشتیبانی دستگاه بازی خود را تجزیه و تحلیل کنید و قالب بافت صحیح را انتخاب کنید. می‌توانید بسته‌های بافت را برای دستگاه‌های رده بالا و پایین با استفاده از Play Asset Delivery ، Addressable یا یک فرآیند دستی‌تر با AssetBundle تقسیم کنید.

شناخته‌شده‌ترین توصیه‌های موجود در « بهینه‌سازی عملکرد بازی موبایل» و « بهینه‌سازی تنظیمات وارد کردن بافت در Unity» را دنبال کنید. سپس این راه‌حل‌ها را امتحان کنید:

  • برای کاهش فضای اشغال شده توسط حافظه، بافت‌ها را با فرمت‌های ASTC فشرده کنید و با نرخ بلوک بالاتر، مانند 8x8، آزمایش کنید.

    اگر استفاده از ETC2 مورد نیاز است، بافت‌های خود را در Atlas بسته‌بندی کنید. قرار دادن چندین بافت در یک بافت واحد، قدرت دو (POT) آن را تضمین می‌کند، می‌تواند فراخوانی‌های ترسیم را کاهش دهد و سرعت رندر را افزایش دهد.

  • قالب و اندازه بافت RenderTarget را بهینه کنید. از بافت‌های با وضوح بالای غیرضروری خودداری کنید. استفاده از بافت‌های کوچکتر در دستگاه‌های تلفن همراه باعث صرفه‌جویی در حافظه می‌شود.

  • برای ذخیره حافظه بافت ، از بسته‌بندی کانال بافت استفاده کنید.

مش‌ها و مدل‌ها

با بررسی تنظیمات اساسی (صفحه ۲۷) شروع کنید و این تنظیمات وارد کردن مش را تأیید کنید:

  • مش‌های اضافی و کوچک‌تر را ادغام کنید.
  • تعداد رأس‌ها را برای اشیاء موجود در صحنه‌ها کاهش دهید (برای مثال، اشیاء ثابت یا دور).
  • گروه‌های سطح جزئیات (LOD) را برای داده‌های با هندسه بالا ایجاد کنید.

متریال‌ها و شیدرها

  • انواع سایه‌زن‌های استفاده نشده را به صورت برنامه‌نویسی شده در طول فرآیند ساخت حذف کنید.
  • انواع سایه‌زن‌های پرکاربرد را در سایه‌زن‌های فوق‌العاده تجمیع کنید تا از تکرار سایه‌زن‌ها جلوگیری شود.
  • بارگذاری پویای سایه‌زن را فعال کنید تا فضای حافظه‌ی زیادی که سایه‌زن‌های از پیش بارگذاری‌شده در VRAM/RAM اشغال می‌کنند، برطرف شود. با این حال، اگر کامپایل سایه‌زن باعث ایجاد وقفه در فریم می‌شود، توجه کنید.
  • از بارگذاری پویای سایه‌زن برای جلوگیری از بارگذاری همه انواع مختلف استفاده کنید. برای اطلاعات بیشتر، به پست وبلاگ «بهبود زمان ساخت سایه‌زن و استفاده از حافظه» مراجعه کنید.
  • با بهره‌گیری از MaterialPropertyBlocks ، از نمونه‌سازی مواد به درستی استفاده کنید.

صوتی

با بررسی تنظیمات اساسی (صفحه ۴۱) شروع کنید و این تنظیمات وارد کردن مش را تأیید کنید:

  • هنگام استفاده از موتورهای صوتی شخص ثالث مانند FMOD یا Wwise، ارجاعات AudioClip بلااستفاده یا اضافی را حذف کنید.
  • پیش‌بارگذاری داده‌های صوتی. پیش‌بارگذاری را برای کلیپ‌هایی که بلافاصله در زمان اجرا یا راه‌اندازی صحنه مورد نیاز نیستند، غیرفعال کنید. این کار به کاهش سربار حافظه در هنگام مقداردهی اولیه صحنه کمک می‌کند.

انیمیشن‌ها

  • تنظیمات فشرده‌سازی انیمیشن یونیتی را طوری تنظیم کنید که تعداد فریم‌های کلیدی به حداقل برسد و داده‌های اضافی حذف شوند.
    • کاهش فریم‌های کلیدی: فریم‌های کلیدی غیرضروری را به طور خودکار حذف می‌کند.
    • فشرده‌سازی کواترنیون: داده‌های چرخش را فشرده می‌کند تا استفاده از حافظه را کاهش دهد

می‌توانید تنظیمات فشرده‌سازی را در تنظیمات وارد کردن انیمیشن (Animation Import Settings) در زیر تب ریگ (Rig) یا انیمیشن (Animation) تنظیم کنید.

  • به جای کپی کردن کلیپ‌های انیمیشن برای اشیاء مختلف، از کلیپ‌های انیمیشن دوباره استفاده کنید.

    از Animator Override Controllers برای استفاده مجدد از یک Animator Controller و جایگزینی کلیپ‌های خاص برای شخصیت‌های مختلف استفاده کنید.

  • انیمیشن‌های مبتنی بر فیزیک را بسازید: اگر انیمیشن‌های شما مبتنی بر فیزیک یا رویه‌ای هستند، آن‌ها را در کلیپ‌های انیمیشن بسازید تا از محاسبات زمان اجرا جلوگیری شود.

  • بهینه‌سازی اسکلت ریگ: از استخوان‌های کمتری در ریگ خود استفاده کنید تا پیچیدگی و مصرف حافظه کاهش یابد.

    • از استخوان‌های اضافی برای اشیاء کوچک یا ثابت خودداری کنید.
    • اگر استخوان‌های خاصی متحرک نیستند یا مورد نیاز نیستند، آنها را از دستگاه جدا کنید.
  • طول کلیپ انیمیشن را کاهش دهید.

    • کلیپ‌های انیمیشن را طوری برش دهید که فقط فریم‌های ضروری را شامل شوند. از ذخیره انیمیشن‌های بلااستفاده یا بیش از حد طولانی خودداری کنید.
    • به جای ساخت کلیپ‌های طولانی برای حرکات تکراری، از انیمیشن‌های تکرارشونده استفاده کنید.
  • مطمئن شوید که فقط یک جزء انیمیشن متصل یا فعال شده است. برای مثال، اگر از Animator استفاده می‌کنید، اجزای انیمیشن Legacy را غیرفعال یا حذف کنید.

  • اگر استفاده از Animator ضروری نیست، از آن اجتناب کنید. برای جلوه‌های ویژه بصری ساده، از کتابخانه‌های Tweening استفاده کنید یا جلوه بصری را در یک اسکریپت پیاده‌سازی کنید. سیستم Animator می‌تواند منابع زیادی را مصرف کند، به خصوص در دستگاه‌های تلفن همراه رده پایین.

  • هنگام مدیریت تعداد زیادی انیمیشن، از سیستم کار برای انیمیشن‌ها استفاده کنید، زیرا این سیستم به طور کامل دوباره طراحی شده است تا از نظر حافظه کارآمدتر باشد.

صحنه‌ها

وقتی صحنه‌های جدید بارگذاری می‌شوند، دارایی‌ها را به عنوان وابستگی به سیستم اضافه می‌کنند. با این حال، بدون مدیریت صحیح چرخه عمر دارایی‌ها ، این وابستگی‌ها توسط شمارنده‌های مرجع پایش نمی‌شوند. در نتیجه، دارایی‌ها ممکن است حتی پس از تخلیه صحنه‌های بلااستفاده، در حافظه باقی بمانند و باعث تکه‌تکه شدن حافظه شوند.

  • از Object Pooling یونیتی برای استفاده مجدد از نمونه‌های GameObject برای عناصر گیم‌پلی تکرارشونده استفاده کنید، زیرا object pooling از یک پشته برای نگهداری مجموعه‌ای از نمونه‌های شیء برای استفاده مجدد استفاده می‌کند و thread safe نیست. Minimizing Instantiate and Destroy هم عملکرد CPU و هم پایداری حافظه را بهبود می‌بخشد.
  • تخلیه دارایی‌ها:
    • دارایی‌ها را به صورت استراتژیک در لحظات کم‌اهمیت‌تر، مانند صفحات شروع یا صفحات بارگذاری، تخلیه کنید.
    • استفاده مکرر از Resources.UnloadUnusedAssets به دلیل عملیات نظارت بر وابستگی‌های داخلی بزرگ، باعث افزایش ناگهانی در پردازش CPU می‌شود.
    • در نشانگر پروفایل GC.MarkDependencies ، افزایش ناگهانی و شدید CPU را بررسی کنید. فرکانس اجرای آن را حذف یا کاهش دهید و به جای تکیه بر تابع همه‌جانبه Resources.UnloadUnusedAssets() منابع خاص را به صورت دستی با استفاده از Resources.UnloadAssets تخلیه کنید.
  • صحنه‌ها را به جای استفاده مداوم از Resources.UnloadUnusedAssets، بازسازی کنید.
  • فراخوانی Resources.UnloadUnusedAssets() برای Addressables می‌تواند ناخواسته بسته‌های بارگذاری‌شده‌ی پویا را از حالت بارگذاری خارج کند. چرخه‌ی حیات دارایی‌های بارگذاری‌شده‌ی پویا را با دقت مدیریت کنید.

متفرقه

  • تکه‌تکه شدن ناشی از انتقال صحنه — وقتی متد Resources.UnloadUnusedAssets() فراخوانی می‌شود، یونیتی موارد زیر را انجام می‌دهد:

    • حافظه را برای دارایی‌هایی که دیگر استفاده نمی‌شوند آزاد می‌کند
    • عملیاتی شبیه به جمع‌آوری زباله را اجرا می‌کند تا توده اشیاء مدیریت‌شده و بومی را برای یافتن دارایی‌های بلااستفاده بررسی کرده و آنها را تخلیه کند.
    • بافت، مش و حافظه دارایی را پاک می‌کند، مشروط بر اینکه هیچ مرجع فعالی وجود نداشته باشد
  • AssetBundle یا Addressable - ایجاد تغییرات در این زمینه پیچیده است و نیاز به تلاش جمعی از سوی تیم برای اجرای استراتژی‌ها دارد. با این حال، هنگامی که این استراتژی‌ها به خوبی اجرا شوند، به طور قابل توجهی استفاده از حافظه را بهبود می‌بخشند، حجم دانلود را کاهش می‌دهند و هزینه‌های ابری را پایین می‌آورند. برای اطلاعات بیشتر در مورد مدیریت دارایی‌ها در Unity با Addressables، به Addressables مراجعه کنید.

  • وابستگی‌های مشترک متمرکز -mdash: وابستگی‌های مشترک، مانند سایه‌زن‌ها، بافت‌ها و فونت‌ها را به صورت سیستماتیک در بسته‌های اختصاصی یا گروه‌های Addressable گروه‌بندی کنید. این کار باعث کاهش تکرار و تضمین تخلیه کارآمد دارایی‌های غیرضروری می‌شود.

  • استفاده از Addressables برای ردیابی وابستگی - Addressables بارگیری و تخلیه را ساده می‌کند و می‌تواند وابستگی‌هایی را که دیگر به آنها ارجاع داده نمی‌شود، به طور خودکار تخلیه کند. انتقال به Addressables برای مدیریت محتوا و حل وابستگی، بسته به مورد خاص بازی، می‌تواند یک راه حل مناسب باشد. زنجیره‌های وابستگی را با ابزار Analyze تجزیه و تحلیل کنید تا موارد تکراری یا وابستگی‌های غیرضروری را شناسایی کنید. به عنوان یک روش جایگزین، اگر از AssetBundles استفاده می‌کنید، به Unity Data Tools مراجعه کنید.

  • TypeTrees - اگر Addressables و AssetBundles بازی شما با استفاده از همان نسخه Unity به عنوان پخش کننده ساخته و مستقر شده‌اند و نیازی به سازگاری با نسخه‌های دیگر پخش کننده ندارند، غیرفعال کردن نوشتن TypeTree را در نظر بگیرید، که باید اندازه بسته و میزان اشغال حافظه توسط شیء فایل سریالی شده را کاهش دهد. فرآیند ساخت را در بسته محلی Addressables تغییر دهید و ContentBuildFlags را روی DisableWriteTypeTree تنظیم کنید.

کدی بنویسید که با زباله‌روب سازگار باشد

یونیتی از جمع‌آوری زباله (GC) برای مدیریت حافظه با شناسایی و آزادسازی خودکار حافظه استفاده نشده استفاده می‌کند. اگرچه GC ضروری است، اما اگر به درستی مدیریت نشود، می‌تواند باعث مشکلات عملکردی (به عنوان مثال، افزایش ناگهانی نرخ فریم) شود، زیرا این فرآیند می‌تواند بازی را به طور موقت متوقف کند و منجر به وقفه در عملکرد و تجربه کاربری نامطلوب شود.

برای تکنیک‌های مفید در مورد کاهش فرکانس تخصیص‌های مدیریت‌شده‌ی هیپ به راهنمای یونیتی و برای مثال‌ها به UnityPerformanceTuningBible ، صفحه ۲۷۱، مراجعه کنید.

  • کاهش تخصیص زباله‌جمع‌کن‌ها:

    • از LINQ، لامبدا و closureها که حافظه هیپ را اختصاص می‌دهند، اجتناب کنید.
    • برای رشته‌های تغییرپذیر، به جای الحاق رشته‌ها، StringBuilder استفاده کنید.
    • با فراخوانی COLLECTIONS.Clear() به جای نمونه‌سازی مجدد، از مجموعه‌ها دوباره استفاده کنید.

    اطلاعات بیشتر در کتاب الکترونیکی راهنمای نهایی برای پروفایلینگ بازی‌های یونیتی موجود است.

  • مدیریت به‌روزرسانی‌های بوم رابط کاربری:

    • تغییرات پویا در عناصر رابط کاربری — وقتی عناصر رابط کاربری مانند ویژگی‌های متن، تصویر یا RectTransform به‌روزرسانی می‌شوند (برای مثال، تغییر محتوای متن، تغییر اندازه عناصر یا متحرک‌سازی موقعیت‌ها)، موتور ممکن است حافظه را برای اشیاء موقت اختصاص دهد.
    • تخصیص رشته‌ها — عناصر رابط کاربری مانند متن اغلب نیاز به به‌روزرسانی رشته‌ها دارند، زیرا رشته‌ها در اکثر زبان‌های برنامه‌نویسی تغییرناپذیر هستند.
    • بوم کثیف - وقتی چیزی روی بوم تغییر می‌کند (مثلاً تغییر اندازه، فعال و غیرفعال کردن عناصر یا تغییر ویژگی‌های طرح‌بندی)، کل بوم یا بخشی از آن ممکن است به عنوان کثیف علامت‌گذاری شده و دوباره ساخته شود. این می‌تواند باعث ایجاد ساختارهای داده موقت (مثلاً داده‌های مش، بافرهای رأس یا محاسبات طرح‌بندی) شود که به تولید زباله می‌افزاید.
    • به‌روزرسانی‌های تکمیلی یا مکرر - اگر بوم تعداد زیادی عنصر داشته باشد یا مرتباً به‌روزرسانی شود (مثلاً هر فریم)، ​​این بازسازی‌ها می‌توانند منجر به از دست رفتن قابل توجه حافظه شوند.
  • فعال کردن GC افزایشی برای کاهش پیک‌های بزرگ جمع‌آوری با پخش کردن پاکسازی‌های تخصیص در چندین فریم. برای بررسی اینکه آیا این گزینه عملکرد و ردپای حافظه بازی شما را بهبود می‌بخشد یا خیر، از Profile استفاده کنید.

  • اگر بازی شما به رویکردی کنترل‌شده نیاز دارد، حالت جمع‌آوری زباله را روی دستی تنظیم کنید. سپس، در صورت تغییر سطح یا در لحظه‌ای دیگر بدون گیم‌پلی فعال، جمع‌آوری زباله را فراخوانی کنید.

  • فراخوانی تابع جمع‌آوری زباله دستی GC.Collect() برای انتقال حالت بازی (برای مثال، تغییر سطح).

  • بهینه‌سازی آرایه‌ها را از شیوه‌های ساده کدنویسی شروع کنید و در صورت لزوم، با استفاده از آرایه‌های بومی یا سایر کانتینرهای بومی برای آرایه‌های بزرگ، این کار را انجام دهید.

  • اشیاء مدیریت‌شده را با استفاده از ابزارهایی مانند Unity Memory Profiler رصد کنید تا ارجاعات اشیاء مدیریت‌نشده‌ای را که پس از تخریب باقی می‌مانند، ردیابی کنید.

    برای ارسال به ابزار گزارش عملکرد جهت رویکرد خودکار، از یک نشانگر پروفایلر استفاده کنید.

جلوگیری از نشت حافظه و تکه تکه شدن

نشت حافظه

در کد C#، وقتی ارجاعی به یک شیء Unity پس از نابودی شیء وجود دارد، شیء پوششی مدیریت‌شده، که به عنوان پوسته مدیریت‌شده شناخته می‌شود، در حافظه باقی می‌ماند. حافظه بومی مرتبط با ارجاع، زمانی آزاد می‌شود که صحنه تخلیه شود یا وقتی شیء بازی که حافظه به آن متصل است یا هر یک از اشیاء والد آن، از طریق متد Destroy() نابود شوند. با این حال، اگر ارجاعات دیگر به صحنه یا شیء بازی پاک نشده باشند، حافظه مدیریت‌شده ممکن است به عنوان یک شیء پوسته نشت‌شده باقی بماند . برای جزئیات بیشتر در مورد اشیاء پوسته مدیریت‌شده، به راهنمای اشیاء پوسته مدیریت‌شده مراجعه کنید.

علاوه بر این، نشت حافظه می‌تواند ناشی از اشتراک رویدادها، لامبداها و کلوژرها، الحاق رشته‌ها و مدیریت نادرست اشیاء جمع‌آوری‌شده باشد:

  • برای شروع، برای مقایسه صحیح اسنپ‌شات‌های حافظه یونیتی، به بخش «یافتن نشت حافظه» مراجعه کنید.
  • اشتراک رویدادها و نشت حافظه را بررسی کنید. اگر اشیاء در رویدادها مشترک شوند (برای مثال، توسط delegates یا UnityEvents) اما قبل از از بین رفتن به درستی اشتراک خود را لغو نکنند، مدیر رویداد یا ناشر ممکن است ارجاعات به آن اشیاء را حفظ کند. این امر از جمع‌آوری زباله توسط آن اشیاء جلوگیری می‌کند و منجر به نشت حافظه می‌شود.
  • رویدادهای کلاس سراسری یا تک‌لایه که در هنگام تخریب شیء ثبت نشده‌اند را رصد کنید. برای مثال، لغو اشتراک یا لغو اتصال delegateها در مخرب‌های شیء.
  • اطمینان حاصل کنید که تخریب اشیاء جمع‌آوری‌شده، ارجاعات به اجزای مش متنی ، بافت‌ها و اشیاء بازی والد را به‌طور کامل بی‌اثر می‌کند.
  • به خاطر داشته باشید که هنگام مقایسه اسنپ‌شات‌های Unity Memory Profiler و مشاهده تفاوت در مصرف حافظه بدون دلیل مشخص ، این تفاوت ممکن است ناشی از درایور گرافیک یا خود سیستم عامل باشد.

تکه‌تکه شدن حافظه

تکه‌تکه شدن حافظه زمانی اتفاق می‌افتد که بسیاری از تخصیص‌های کوچک به ترتیب تصادفی آزاد می‌شوند. تخصیص‌های هیپ به صورت متوالی انجام می‌شوند، به این معنی که قطعات حافظه جدید زمانی ایجاد می‌شوند که قطعه قبلی فضای کافی نداشته باشد. در نتیجه، اشیاء جدید قسمت‌های خالی قطعات قدیمی را پر نمی‌کنند و منجر به تکه‌تکه شدن می‌شوند. علاوه بر این، تخصیص‌های موقت بزرگ می‌توانند باعث تکه‌تکه شدن دائمی در طول دوره بازی شوند.

این مسئله به ویژه زمانی مشکل‌ساز می‌شود که تخصیص‌های بزرگ کوتاه‌مدت در نزدیکی تخصیص‌های بلندمدت انجام شوند.

تخصیص‌های گروهی بر اساس طول عمرشان؛ در حالت ایده‌آل، تخصیص‌های طولانی‌مدت باید با هم و در اوایل چرخه عمر برنامه انجام شوند.

ناظران و مدیران رویداد

  • علاوه بر مشکلی که در بخش نشت حافظه به آن اشاره شد، با گذشت زمان، نشت حافظه می‌تواند با اختصاص دادن حافظه بلااستفاده به اشیاء بلااستفاده، به تکه‌تکه شدن سیستم کمک کند.
  • اطمینان حاصل کنید که تخریب اشیاء جمع‌آوری‌شده، ارجاعات به اجزای مش متنی ، بافت‌ها و GameObjects والد را به‌طور کامل بی‌اثر می‌کند.
  • مدیران رویداد اغلب لیست‌ها یا دیکشنری‌هایی را برای مدیریت اشتراک‌های رویداد ایجاد و ذخیره می‌کنند. اگر این موارد در طول زمان اجرا به صورت پویا افزایش و کاهش یابند، می‌توانند به دلیل تخصیص‌ها و آزادسازی‌های مکرر، در قطعه قطعه شدن حافظه نقش داشته باشند.

کد

  • گاهی اوقات کوروتین‌ها حافظه اختصاص می‌دهند، که می‌توان به راحتی با ذخیره کردن دستور return از IEnumerator به جای تعریف یک دستور جدید در هر بار، از این امر جلوگیری کرد.
  • به طور مداوم وضعیت چرخه حیات اشیاء ادغام شده را رصد کنید تا از ارجاعات شبح‌وار UnityEngine.Object جلوگیری شود.

دارایی‌ها

  • برای تجربه‌های بازی مبتنی بر متن، از سیستم‌های پشتیبان پویا استفاده کنید تا از پیش‌بارگذاری همه فونت‌ها برای موارد چندزبانه جلوگیری شود.
  • دارایی‌ها (مثلاً بافت‌ها و ذرات) را بر اساس نوع و چرخه عمر مورد انتظار، سازماندهی کنید.
  • دارایی‌ها را با ویژگی‌های چرخه عمر بلااستفاده، مانند تصاویر رابط کاربری تکراری و مش‌های استاتیک، فشرده کنید.

تخصیص‌های مبتنی بر طول عمر

  • دارایی‌های با عمر طولانی را در ابتدای چرخه حیات برنامه اختصاص دهید تا تخصیص‌های فشرده تضمین شود.
  • از NativeCollections یا تخصیص‌دهنده‌های سفارشی برای ساختارهای داده‌ی حافظه‌محور یا گذرا (مثلاً کلاسترهای فیزیک) استفاده کنید.

فایل‌های اجرایی بازی و افزونه‌ها نیز بر میزان استفاده از حافظه تأثیر می‌گذارند.

فراداده IL2CPP

IL2CPP در زمان ساخت، برای هر نوع (مثلاً کلاس‌ها، ژنریک‌ها و نمایندگان) فراداده تولید می‌کند که سپس در زمان اجرا برای بازتاب، بررسی نوع و سایر عملیات خاص زمان اجرا استفاده می‌شود. این فراداده در حافظه ذخیره می‌شود و می‌تواند به طور قابل توجهی در کل فضای حافظه برنامه نقش داشته باشد. حافظه پنهان فراداده IL2CPP سهم قابل توجهی در زمان‌های مقداردهی اولیه و بارگذاری دارد. علاوه بر این، IL2CPP عناصر فراداده خاصی (مثلاً انواع ژنریک یا اطلاعات سریالی) را حذف نمی‌کند، که می‌تواند منجر به استفاده بیش از حد از حافظه شود. این امر با استفاده تکراری یا زائد از نوع در پروژه تشدید می‌شود.

ابرداده‌های IL2CPP را می‌توان با موارد زیر کاهش داد:

  • اجتناب از استفاده از APIهای بازتابی ، زیرا می‌توانند سهم قابل توجهی در تخصیص فراداده IL2CPP داشته باشند.
  • غیرفعال کردن بسته‌های داخلی
  • پیاده‌سازی اشتراک‌گذاری کامل ژنریک در یونیتی ۲۰۲۲، که باید به کاهش سربار ناشی از ژنریک‌ها کمک کند. با این حال، برای کمک به کاهش هرچه بیشتر تخصیص‌ها، استفاده از ژنریک‌ها را کاهش دهید.

حذف کد

فراتر از کاهش اندازه ساخت، حذف کد، میزان استفاده از حافظه را نیز کاهش می‌دهد. هنگام ساخت در برابر بک‌اند اسکریپت‌نویسی IL2CPP، حذف مدیریت‌شده بایت‌کد (که به طور پیش‌فرض فعال است) کد استفاده نشده را از اسمبلی‌های مدیریت‌شده حذف می‌کند. این فرآیند با تعریف اسمبلی‌های ریشه و سپس استفاده از تحلیل استاتیک کد برای تعیین اینکه آن اسمبلی‌های ریشه از چه کد مدیریت‌شده دیگری استفاده می‌کنند، کار می‌کند. هر کدی که قابل دسترسی نباشد حذف می‌شود. برای اطلاعات بیشتر در مورد حذف مدیریت‌شده کد، به TTales from the optimization trenches: Better manageed code stripping with Unity 2020 LTS blog post و مستندات حذف مدیریت‌شده کد مراجعه کنید.

تخصیص‌دهنده‌های بومی

برای تنظیم دقیق تخصیص‌دهنده‌های حافظه ، از تخصیص‌دهنده‌های حافظه بومی استفاده کنید. اگر بازی با کمبود حافظه مواجه است، از بلوک‌های حافظه کوچکتر استفاده کنید، حتی اگر این شامل تخصیص‌دهنده‌های کندتر باشد. برای کسب اطلاعات بیشتر به مثال تخصیص‌دهنده هیپ پویا مراجعه کنید.

مدیریت افزونه‌ها و SDKهای بومی

  • افزونه مشکل‌ساز را پیدا کنید - هر افزونه را حذف کنید و عکس‌های فوری حافظه بازی را با هم مقایسه کنید. این شامل غیرفعال کردن بسیاری از قابلیت‌های کد با استفاده از Scripting Define Symbols و بازسازی کلاس‌های بسیار جفت‌شده با رابط‌ها می‌شود. کد خود را با الگوهای برنامه‌نویسی بازی ارتقا دهید تا فرآیند غیرفعال کردن وابستگی‌های خارجی بدون غیرقابل بازی شدن بازی شما تسهیل شود.

  • با نویسنده افزونه یا SDK تماس بگیرید — اکثر افزونه‌ها متن‌باز نیستند.

  • بازتولید میزان استفاده از حافظه افزونه — می‌توانید یک افزونه ساده بنویسید (از این افزونه Unity به عنوان مرجع استفاده کنید) که تخصیص حافظه را انجام می‌دهد. اسنپ‌شات‌های حافظه را با استفاده از اندروید استودیو بررسی کنید (زیرا Unity این تخصیص‌ها را ردیابی نمی‌کند) یا کلاس MemoryInfo و متد Runtime.totalMemory() را در همان پروژه فراخوانی کنید.

یک افزونه‌ی یونیتی، حافظه‌ی جاوا و حافظه‌ی بومی را اختصاص می‌دهد؛ نحوه‌ی انجام این کار به این صورت است:

جاوا

byte[] largeObject = new byte[1024 * 1024 * megaBytes];
list.add(largeObject);

بومی

char* buffer = new char[megabytes * 1024 * 1024];

// Random data to fill the buffer
for (int i = 1; i < megabytes * 1024 * 1024; ++i) {
   buffer[i] = 'A' + (i % 26); // Fill with letters A-Z
}