Efektywnie zarządzaj pamięcią w grach

Na platformie Android system stara się wykorzystać jak najwięcej pamięci systemowej (RAM) i wykonuje różne optymalizacje pamięci, aby w razie potrzeby zwolnić miejsce. Te optymalizacje mogą mieć negatywny wpływ na grę, ponieważ mogą ją spowolnić lub całkowicie ją zatrzymać. Więcej informacji o tych optymalizacjach znajdziesz w temacie Przydzielanie pamięci między procesami.

Na tej stronie znajdziesz informacje o tym, jak uniknąć problemów z niedostateczną ilością pamięci w grze.

Odpowiedź na onTrimMemory()

System używa onTrimMemory(), aby powiadamiać aplikację o zdarzeniach cyklu życia, które stwarzają dla niej dobrą okazję do dobrowolnego zmniejszenia zużycia pamięci i uniknięcia zabicia przez funkcję LMK (low-memory killer), która zwalnia pamięć dla innych aplikacji.

Jeśli aplikacja zostanie zamknięta w tle, następnym razem, gdy użytkownik ją uruchomi, będzie to uruchomienie „na zimno”. Aplikacje, które zmniejszają użycie pamięci podczas przechodzenia do trybu w tle, są mniej narażone na zabicie w tle.

Odpowiadając na zdarzenia przycinania, najlepiej zwolnić duże przydziały pamięci, które nie są potrzebne od razu i mogą zostać odtworzone na żądanie. Jeśli na przykład Twoja aplikacja ma pamięć podręczną bitmap, które zostały zdekodowane z lokalnie zapisanych skompresowanych obrazów, często warto przyciąć lub wyczyścić tę pamięć podręczną w odpowiedzi na TRIM_MEMORY_UI_HIDDEN.

Kotlin

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.
        }
    }
}

Java

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.
            }
        }
    }
}

C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

class LowMemoryTrigger : MonoBehaviour
{
    private void Start()
    {
        Application.lowMemory += OnLowMemory;
    }
    private void OnLowMemory()
    {
        // Respond to low memory condition (e.g., Resources.UnloadUnusedAssets())
    }
}

Korzystanie z wersji beta interfejsu Memory Advice API

Interfejs Memory Advice API został opracowany jako alternatywa dla metody onTrimMemory, która ma znacznie większą czułość i dokładność w prognozowaniu nadchodzących LMK. Interfejs API osiąga to, szacując ilość zasobów pamięci, które są używane, a następnie powiadami aplikację, gdy przekroczone zostaną określone progi. Interfejs API może też przekazywać do aplikacji szacowany odsetek wykorzystania pamięci. Do zarządzania pamięcią możesz używać interfejsu Memory Advice API zamiast zdarzeń onTrimMemory.

Aby korzystać z interfejsu Memory Advice API, zapoznaj się z wprowadzeniem.

Ostrożnie korzystaj z budżetów pamięci

Określ ilość pamięci w sposób ostrożny, aby uniknąć jej braku. Oto kilka kwestii do rozważenia:

  • Rozmiar fizycznej pamięci RAM: gry często wykorzystują od ¼ do ½ fizycznej ilości pamięci RAM na urządzeniu.
  • Maksymalny rozmiar zRAM: im więcej zRAM, tym więcej pamięci może przydzielić gra. Ta kwota może się różnić w zależności od urządzenia. Aby znaleźć tę wartość, poszukaj elementu SwapTotal w elementzie /proc/meminfo.
  • Użycie pamięci przez system operacyjny: urządzenia, które przydzielają więcej pamięci RAM procesom systemowym, pozostawiają mniej pamięci dla gry. System zabija proces gry, zanim zabije procesy systemowe.
  • Wykorzystanie pamięci przez zainstalowane aplikacje: przetestuj grę na urządzeniach z dużą liczbą zainstalowanych aplikacji. Aplikacje do mediów społecznościowych i czatu muszą działać bez przerwy i wpływają na ilość wolnej pamięci.

Jeśli nie możesz się zdecydować na konserwatywny budżet pamięci, zastosuj bardziej elastyczne podejście. Jeśli system ma problemy z niedostatkiem pamięci, zmniejsz ilość pamięci używanej przez grę. Na przykład w odpowiedzi na onTrimMemory() możesz przydzielić tekstury o niższej rozdzielczości lub przechowywać mniej shaderów. To dynamiczne podejście do alokacji pamięci wymaga od dewelopera więcej pracy, zwłaszcza na etapie projektowania gry.

Unikaj przeciążeń

Thrashing występuje, gdy wolna pamięć jest niewielka, ale nie na tyle, aby zakończyć działanie gry. W tej sytuacji kswapd odzyskał strony, których nadal potrzebuje gra, więc próbuje ponownie je załadować z pamięci. Nie ma wystarczająco dużo miejsca, więc strony są ciągle zastępowane (ciągłe zastępowanie). Śledzenie systemu rejestruje tę sytuację jako wątek, w którym kswapd działa nieprzerwanie.

Jednym z objawów thrashingu jest długi czas generowania klatek – prawdopodobnie sekunda lub dłużej. Aby rozwiązać ten problem, zmniejsz zużycie pamięci przez grę.

Korzystanie z dostępnych narzędzi

Android ma zbiór narzędzi, które pomagają zrozumieć, jak system zarządza pamięcią.

Meminfo

Narzędzie to zbiera statystyki dotyczące pamięci, aby pokazać, ile pamięci PSS zostało przydzielonych i do jakich kategorii.

Wydrukuj statystyki meminfo na jeden z tych sposobów:

  • Użyj polecenia adb shell dumpsys meminfo package-name.
  • Użyj wywołania MemoryInfo z interfejsu Android Debug API.

Statystyka PrivateDirty pokazuje ilość pamięci RAM w procesie, która nie może być przenoszona na dysk i nie jest udostępniana innym procesom. Większość tej kwoty staje się dostępna dla systemu, gdy proces zostanie zatrzymany.

Punkty śledzenia pamięci

Punkty śledzenia pamięci śledzą ilość pamięci RSS używanej przez grę. Obliczanie wykorzystania pamięci przez RSS jest znacznie szybsze niż obliczenie wykorzystania przez PSS. Ponieważ obliczenia są szybsze, RSS zapewnia dokładniejsze pomiary zmian rozmiaru pamięci, co pozwala na dokładniejsze pomiary szczytowego wykorzystania pamięci. Dzięki temu łatwiej zauważyć szczyty, które mogą spowodować zapełnienie się pamięci.

Perfekcja i długie ślady

Perfetto to zestaw narzędzi do zbierania informacji o wydajności i pamięci na urządzeniu oraz wyświetlania ich w interfejsie internetowym. Obsługuje dowolnie długie ścieżki, dzięki czemu możesz sprawdzać, jak zmienia się RSS w czasie. Możesz też wysyłać zapytania SQL do danych generowanych przez usługę na potrzeby przetwarzania offline. Włącz długie śledzenia w aplikacji śledzenia systemu. Upewnij się, że dla śledzenia włączona jest kategoria memory:Memory.

heapprofd

heapprofd to narzędzie do śledzenia pamięci, które jest częścią usługi Peretto. To narzędzie może pomóc Ci znaleźć wycieki pamięci, pokazując, gdzie pamięć została przydzielona za pomocą malloc. heapprofd można uruchomić za pomocą skryptu Pythona. Ponieważ narzędzie ma niskie obciążenie, nie wpływa na wydajność, tak jak inne narzędzia, np. Malloc Debug.

bugreport

bugreport to narzędzie do rejestrowania informacji, które pozwala sprawdzić, czy gra uległa awarii z powodu braku pamięci. Dane wyjściowe tego narzędzia są znacznie bardziej szczegółowe niż te z logcat. Jest to przydatne podczas debugowania pamięci, ponieważ pokazuje, czy gra uległa awarii z powodu braku pamięci lub czy została zamknięta przez LMK.

Więcej informacji znajdziesz w artykule Zapisywanie i czytanie raportów o błędach.