نظارت بر میزان استفاده از حافظه

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

درک RSS و معیارهای swap

برای تحلیل و اشکال‌زدایی مؤثر رفتار حافظه بازی خود، باید معیارهای فنی دقیقی را که پلتفرم اندروید برای مدیریت حافظه استفاده می‌کند، درک کنید. برای اطلاعات پیش‌زمینه دقیق در مورد نحوه پردازش و نظارت بر این پارامتر تله‌متری در عمل، به مستندات Android Vitals - Memory usage (anonymous RSS + swap) مراجعه کنید.

۱. RSS ناشناس (RssAnon)

اندازه مجموعه مقیم (RSS) بخشی از حافظه اشغال شده توسط یک فرآیند را که در رم فیزیکی دستگاه نگهداری می‌شود، اندازه‌گیری می‌کند. RSS به حافظه فایل-بک‌آپ و حافظه ناشناس تقسیم می‌شود. معیار حافظه اندروید به شدت بر RSS ناشناس تمرکز دارد:

  • شامل چه مواردی می‌شود : صفحات حافظه‌ای که مستقیماً توسط فرآیند بازی شما اختصاص داده شده‌اند و به یک فایل فیزیکی در حافظه متصل نیستند. این صفحات شامل هیپ‌های جاوا یا کاتلین، پشته‌های اجرای نخ و از همه مهم‌تر، تخصیص‌های حافظه بومی (مانند تخصیص‌دهنده‌های سفارشی موتور C++ یا بلوک‌های حافظه درخواست شده با استفاده از malloc بومی یا منطق جدید و آلوده بازی) می‌شوند. برای اطلاعات بیشتر در مورد این معیار، به فرهنگ لغت حافظه فرآیند (RSS) مراجعه کنید.
  • دلیل اهمیت : موتورهای بازی از حافظه‌های داخلی عظیمی برای مدیریت فیزیک، رندرینگ و منطق استفاده می‌کنند. از آنجا که این حافظه‌ها توسط فایل‌ها پشتیبانی نمی‌شوند، کاملاً در RSS ناشناس قرار دارند و بخش عمده‌ای از ردپای فیزیکی بازی شما را تشکیل می‌دهند.

۲. سوآپ فشرده نشده (VmSwap)

اندروید به دلیل فرسودگی حافظه فلش و محدودیت‌های تأخیر، از فضای swap سنتی مبتنی بر دیسک پشتیبانی نمی‌کند. در عوض، از zRAM (مخفف Uncompressed Swap) استفاده می‌کند:

  • شامل چه مواردی می‌شود : وقتی فشار رم فیزیکی افزایش می‌یابد، سرویس مدیریت حافظه هسته، صفحات ناشناس غیرفعال را فشرده کرده و آنها را به یک بخش اختصاصی و فشرده نشده از رم فیزیکی (zRAM) منتقل می‌کند.
  • محاسبه‌ی معیار : سیستم این را بر اساس اندازه‌ی فشرده‌نشده (VmSwap) پیگیری می‌کند تا میزان حافظه‌ی فیزیکی مورد نیاز بازی را ارزیابی کند. اگر بازی شما حافظه‌ای اختصاص دهد و سیستم آن را به zRAM منتقل کند، باز هم این مقدار در مجموع فضای اشغال‌شده توسط حافظه‌ی بازی شما محاسبه می‌شود.

۳. حالت‌های فرآیند

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

  • شامل چه مواردی می‌شود : پیش‌زمینه، سرویس‌های قابل درک، پس‌زمینه و ذخیره‌سازی شده.
  • دلیل اهمیت : حالت‌های مختلف فرآیند، تأثیرات متفاوتی بر مدیریت حافظه سیستم عامل اندروید دارند. اگر هر یک از SDKهای شخص ثالث ناخواسته یک وظیفه پس‌زمینه را فعال کنند، ممکن است متوجه نشوید که بازی شما با یک حالت فرآیند حساس در حال اجرا است. با استفاده از RunningAppProcessInfo اجرا شدن بازی خود در پس‌زمینه را بررسی کنید.

رابط‌های برنامه‌نویسی کاربردی (API)

اندروید APIهای سیستمی را ارائه می‌دهد که به بازی شما اجازه می‌دهد به صورت پویا به فشار حافظه پاسخ دهد و در زمان اجرا، تشخیص‌های دقیقی از حافظه را ثبت کند.

پاسخ به رویدادهای اصلاح حافظه

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

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

هنگام پاسخ به رویدادهای trim، تخصیص‌های حافظه بزرگ و قابل بازسازی که فوراً مورد نیاز نیستند را آزاد کنید:

  • مثال: در پاسخ به TRIM_MEMORY_UI_HIDDEN ، بیت‌مپ‌های ذخیره‌شده در حافظه پنهان (رمزگشایی‌شده از حافظه محلی) را حذف یا پاک کنید.

کاتلین

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }
        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

جاوا

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (level) {
            if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
                // Release memory related to UI elements, such as bitmap caches.
            }
            if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
                // Release memory related to background processing, such as by
                // closing a database connection.
            }
        }
    }
}

ProfilingManager

رابط برنامه‌نویسی کاربردی ProfilingManager که در اندروید ۱۵ (سطح API ۳۵) معرفی شد، به برنامه‌ها اجازه می‌دهد تا اسنپ‌شات‌های تعریف‌شده توسط برنامه‌نویسی (مانند پروفایل‌های هیپ، ردپاهای سیستم و داده‌های هیپ جاوا) را مستقیماً در زمان اجرا ضبط کنند.

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

نکته: موتورهای بازی مدرن (مانند Unity یا Unreal) با استفاده از mmap و پرچم MAP_ANONYMOUS، عملکرد اجرا را با پیش‌تخصیص بلوک‌های حافظه مجازی عظیم از هسته مدیریت می‌کنند. سپس موتورها از تخصیص‌دهنده‌های فرعی سفارشی (به عنوان مثال، مدیر حافظه بومی Unity یا BinnedAllocators Unreal) برای تقسیم و تخصیص داخلی بلوک‌های حافظه استفاده می‌کنند.

اطلاعات خروج از برنامه

اگر بازی شما در پس‌زمینه خاتمه یافته یا به دلیل نقض محدودیت‌های حافظه پردازش فردی از کار افتاده است، مکانیزم‌های استاندارد جاوا یا مکانیزم‌های بومی تخلیه خرابی (مانند Firebase Crashlytics) این رویداد را ثبت نمی‌کنند. برای پرس‌وجو و ثبت این خاتمه‌ها به صورت برنامه‌نویسی، توسعه‌دهندگان باید از API ApplicationExitInfo در هنگام راه‌اندازی بازی استفاده کنند.

  • پیاده‌سازی: در شروع، برای بازیابی دلایل خروج از جلسات اخیر، ActivityManager.getHistoricalProcessExitReasons() را فراخوانی کنید.
  • دلایل خروج از حافظه کلیدی:
    • REASON_LOW_MEMORY : نشان می‌دهد که این فرآیند توسط Low Memory Killer (LMK) سیستم خاتمه یافته است. این خاتمه زمانی رخ می‌دهد که فشار حافظه در کل دستگاه زیاد باشد و سیستم عامل باید RAM را بازیابی کند. این دلیل خروج نشان می‌دهد که ردپای پس‌زمینه بازی شما برای همزیستی با سایر برنامه‌ها بسیار بزرگ است.
    • REASON_MEMORY_LIMITER (اندروید ۱۷ (سطح API ۳۷) و بالاتر): نشان می‌دهد که فرآیند به طور خاص به دلیل عبور از محدودیت حافظه cgroup (RssAnon + VmSwap) که توسط محدودکننده حافظه پلتفرم تعیین شده است، از بین رفته است. این خاتمه می‌تواند حتی اگر حافظه فیزیکی کافی روی دستگاه باقی مانده باشد، اتفاق بیفتد و نشان‌دهنده نقض مستقیم محدودیت‌های فرآیند است.

از ابزارهای موجود استفاده کنید

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

meminfo

این ابزار آمار حافظه را جمع‌آوری می‌کند تا نشان دهد چه مقدار حافظه PSS اختصاص داده شده و برای چه دسته‌هایی استفاده شده است.

آمار meminfo را به یکی از روش‌های زیر چاپ کنید:

  • از دستور adb shell dumpsys meminfo package-name استفاده کنید.
  • از فراخوانی MemoryInfo از Android Debug API استفاده کنید.

آمار PrivateDirty میزان رم درون فرآیند را نشان می‌دهد که نمی‌توان آن را به دیسک منتقل کرد و با هیچ فرآیند دیگری به اشتراک گذاشته نشده است. بخش عمده‌ای از این مقدار با از بین رفتن آن فرآیند، در دسترس سیستم قرار می‌گیرد.

نقاط ردیابی حافظه

نقاط ردیابی حافظه، میزان حافظه RSS مورد استفاده بازی شما را ردیابی می‌کنند. محاسبه میزان استفاده از حافظه RSS بسیار سریع‌تر از محاسبه میزان استفاده از PSS است. از آنجا که محاسبه آن سریع‌تر است، RSS جزئیات دقیق‌تری از تغییرات در اندازه حافظه را برای اندازه‌گیری دقیق‌تر اوج استفاده از حافظه نشان می‌دهد. بنابراین، تشخیص اوج‌هایی که می‌توانند باعث اتمام حافظه بازی شوند، آسان‌تر است.

پرفتو

Perfetto مجموعه‌ای از ابزارها برای جمع‌آوری اطلاعات عملکرد و حافظه در یک دستگاه و نمایش آن در یک رابط کاربری مبتنی بر وب است. این ابزار از ردیابی‌های دلخواه طولانی پشتیبانی می‌کند، بنابراین می‌توانید نحوه تغییرات RSS را در طول زمان مشاهده کنید. همچنین می‌توانید برای پردازش آفلاین، کوئری‌های SQL را روی داده‌هایی که تولید می‌کند، صادر کنید. ردیابی‌های طولانی را از برنامه System Tracing فعال کنید. مطمئن شوید که دسته memory:Memory برای ردیابی فعال شده است. برای ابزار دقیق حافظه سفارشی در توسعه و آزمایش، می‌توانید از API (Beta) heapprofd نیز استفاده کنید.

RssAnon را بررسی کنید و در Perfetto آن را جایگزین کنید

برای بررسی تأثیر حافظه ناشناس و مبادله zRAM بازی خود، فایل ردیابی خود را در رابط کاربری مبتنی بر وب در ui.perfetto.dev بارگذاری کنید و این تکنیک‌های تحلیلی را که برای مطالعات موردی حافظه عمیق طراحی شده‌اند، دنبال کنید (برای جزئیات بیشتر به مطالعات موردی تحلیل حافظه Perfetto مراجعه کنید):

۱. نمایش شمارنده‌های حافظه روی تایم‌لاین

  • فرآیند خود را پیدا کنید: در فهرست پیمایش، نام بسته یا فرآیند بازی خود را جستجو کنید.
  • گسترش گروه مسیر: روی ردیف فرآیند خود کلیک کنید تا مسیرهای رشته‌ای آن گسترش یابد و زیرگروهی با نام Memory را پیدا کنید.
  • آهنگ ها را تجزیه و تحلیل کنید:
    • mem.rss.anon (RSS ناشناس) : این نمودار خطی، میزان اشغال حافظه فیزیکی رم توسط حافظه‌های مدیریت نشده بازی شما را به صورت بلادرنگ نشان می‌دهد. این جدول زمانی را در حین بارگذاری صحنه‌ها، پنجره‌های بازشو رابط کاربری یا انتقال‌های گیم‌پلی رصد کنید تا اوج تخصیص حافظه را بررسی کنید.
    • mem.swap (Compressed Swap یا VmSwap) : این نمودار اندازه بلوک‌های حافظه از پیش فشرده‌شده منتقل‌شده به zRAM را نشان می‌دهد. فعالیت بالای swap همزمان با گیم‌پلی نشان می‌دهد که بازی شما روی یک دستگاه با حافظه محدود اجرا می‌شود و سیستم به‌طور فعال در حال فشرده‌سازی فایل‌های پس‌زمینه است.

۲. اجرای کوئری‌های SQL (پردازنده ردیابی) برای تجزیه و تحلیل دقیق آفلاین، می‌توانید کوئری‌های SQL را مستقیماً درون کنسول رابط کاربری Perfetto اجرا کنید یا از کتابخانه مستقل پایتون Trace Processor برای محاسبه پیک‌های آماری استفاده کنید.

  • یافتن حداکثر تخصیص RSS ناشناس:

    SELECT
      max(value) / 1024 / 1024 AS max_rss_anon_mb
    FROM counter
    JOIN counter_track ON counter.track_id = counter_track.id
    WHERE counter_track.name = 'mem.rss.anon'
      AND counter_track.upid IN (
        SELECT upid FROM process WHERE name = 'your.game.package.name'
      );
    
  • مرتبط کردن RssAnon و VmSwap در هر زمان مشخص:

    SELECT
      ts,
      track.name AS metric_type,
      value / 1024 / 1024 AS size_mb
    FROM counter
    JOIN counter_track track ON counter.track_id = track.id
    WHERE (track.name = 'mem.rss.anon' OR track.name = 'mem.swap')
      AND track.upid IN (
        SELECT upid FROM process WHERE name = 'your.game.package.name'
      )
    ORDER BY ts ASC;
    

برای جزئیات بیشتر در مورد بررسی فایل‌های ردیابی با استفاده از اندروید استودیو، به بخش «بازرسی ردیابی‌های سیستم: حافظه پردازش (RSS)» مراجعه کنید. برای جزئیات بیشتر در مورد اسکریپت‌نویسی پروفایل‌های حافظه، به بخش «ضبط تخصیص‌های بومی» مراجعه کنید.

تایید شده

heapprofd یک ابزار ردیابی حافظه است که بخشی از Perfetto می‌باشد. این ابزار می‌تواند با نشان دادن محل تخصیص حافظه با استفاده از malloc ، به شما در یافتن نشت حافظه کمک کند. heapprofd می‌توان با استفاده از یک اسکریپت پایتون اجرا کرد و از آنجا که این ابزار سربار کمی دارد، مانند ابزارهای دیگر مانند Malloc Debug بر عملکرد تأثیر نمی‌گذارد.

گزارش اشکال

bugreport ابزاری برای ثبت وقایع است که به شما کمک می‌کند بفهمید آیا بازی شما به دلیل کمبود حافظه از کار افتاده است یا خیر. خروجی این ابزار بسیار دقیق‌تر از logcat است. این ابزار برای اشکال‌زدایی حافظه مفید است زیرا نشان می‌دهد که آیا بازی شما به دلیل کمبود حافظه از کار افتاده است یا اینکه توسط LMK از کار افتاده است.

برای اطلاعات بیشتر، به بخش «ضبط و خواندن گزارش‌های اشکال» مراجعه کنید.

ابزارهای موتور بازی

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

وحدت

در محیط موتور یونیتی، می‌توانید با استفاده از ابزارها و کلاس‌های پروفایلینگ بومی یونیتی، میزان اشغال فضای حافظه RSS + Swap توسط Android Anonymous را در زمان اجرا با قابلیت اطمینان بالا (که معمولاً واریانسی کمتر از 10٪ در مقایسه با مقادیر واقعی سطح سیستم عامل نشان می‌دهد) به دقت تخمین بزنید.

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

  • رابط برنامه‌نویسی کاربردی پروفایلر یونیتی : شما می‌توانید با پرس‌وجو از معیارهای موتور اصلی، میزان حافظه مدیریت‌نشده بازی خود را در زمان اجرا به صورت تقریبی تخمین بزنید:
    • استفاده از کلاس Profiler : با جمع کردن مقادیر Profiler.GetTotalReservedMemoryLong() و Profiler.GetMonoHeapSizeLong() کل تخصیص حافظه را پیگیری کنید.
    • استفاده از کلاس ProfilerRecorder : نظارت پویا بر دسته‌بندی‌های حافظه. برای ایجاد یک تقریب پایه قابل اعتماد، Total Reserved Memory (در نسخه‌های آزمایشی) را دریافت کنید یا Gfx Reserved Memory را از آن کم کنید (در نسخه‌های آزمایشی) تا اجزای حافظه گرافیکی file-backed را حذف کنید.
  • پروفایلر حافظه یونیتی : برای شناسایی و اشکال‌زدایی نشت حافظه به صورت آفلاین، یک اسنپ‌شات از حافظه تهیه کنید و نمودار حافظه مقیم روی دستگاه را که در بخش «تمام حافظه» یافت می‌شود، بررسی کنید. برای محاسبه تقریبی میزان اشغال فضای حافظه، مجموع دسته‌های زیر را با هم جمع کنید: بدون ردیابی، زمان اجرای اندروید، بومی و مدیریت‌شده.
    • محدودیت zRAM : در شرایط کمبود حافظه، هسته اندروید می‌تواند صفحات حافظه غیرفعال را در فضای swap (zRAM) فشرده کند. از آنجا که Unity Memory Profiler نمی‌تواند پارامترهای swap در سطح سیستم عامل را تشخیص دهد، ممکن است در صحنه‌های سنگین حافظه، اختلافات جزئی در ردپا مشاهده کنید. برای تأیید مقادیر دقیق، تخمین‌های خود را با Perfetto مقایسه کنید.