Autor Wątek: Redukcja zużycia pamięci w grze - jakieś porady?  (Przeczytany 7461 razy)

Offline Xirdus

  • Redaktor

# Kwiecień 13, 2014, 17:32:43
Mam pewien specyficzny problem. Kumpel zrobił grę - przygodówkę 2D z użyciem SDL2. Gra jest bardzo fajna, świetnie wygląda, tylko że zużywa 650-950MB RAM. Niezbyt mi się to podoba. Jednak kiedy zacząłem grzebać w kodzie (open source), nie znalazłem żadnych oczywistych błędów, które niepotrzebnie zwiększałyby drastycznie zużycie pamięci. Tak więc zostaje tylko przerobienie logiki kodu i kompleksowa optymalizacja. Nie mam jednak w tym wprawy, tak więc proszę was o porady.

1. Kod jest w C++, jednak pisany w stylu Java - czyli dosłownie każdy obiekt jest trzymany przez wskaźnik, tworzony przez new i usuwany przez delete. Czy może to wpłynąć aż tak znacząco na zużycie pamięci?

2. Gra posiada bardzo dużo unikalnych obiektów, a lokacje to przede wszystkim wielkie obrazy tła. Ze względów wydajnościowych wszystkie elementy potrzebne w danej scenie gra preloaduje na wejściu, mimo że nie wszystkie są naraz używane. Czy istnieje jakiś bardziej efektywny sposób trzymania obrazów w pamięci niż prosta bitmapa, taki, który nie wpłynie na wydajność ani nie pogorszy jakości grafiki (jakość grafiki to główny punkt marketingu)?

3. Gra korzysta z SDL2 oraz SDL2_img, każdy spritesheet trzyma w osobnym SDL_Texture. Czy przez jakieś bardziej optymalne ułożenie sprite'ów dałoby się zmniejszyć zużycie pamięci (nie wiem, np. wspólny atlas tekstur czy bardziej kwadratowe ułożenie elementów)?

4. Czy są jakieś nieoczywiste ale ogólnie znane triki związane z SDL2 które pozwalają ograniczyć zużycie pamięci?

5. Niektóre animacje są trzymanie nie jako spritesheety, tylko jako filmy - pliki .mov (VLC mówi, że kodekiem jest Apple Quicktime RLE Video). Z tego co zrozumiałem z kodu, przy wczytywaniu uruchamiany jest libavcodec który dekoduje każdą klatkę, która potem jest kopiowana do osobnej SDL_Texture. Czy rezygnacja z filmów i użycie w ich miejsce spritesheetów z identyczną zawartością mogłoby oszczędzić pamięci?

6. Jak wydajne byłoby preloadowanie plików obrazów w pamięci jako PNG i dekompresja dopiero kiedy faktycznie jest używany i usuwanie zaraz gdy przestanie? O ile będzie to szybsze od wczytywania pliku za każdym razem z dysku i o ile wolniejsze od trzymania zdekompresowanej bitmapy?

7. Gra trzyma zasoby w zipie, do wczytywania plików używa biblioteki miniz. Ma ktoś z nią doświadczenie? Czy po otworzeniu archiwum przez mz_zip_reader_init_file() przechowuje ona dużo rzeczy w pamięci?

8. Z powodu niebotycznej ilości new i delete, Dr. Memory działa tak wolno, że tracę cierpliwość zanim nawet się włączy menu. Czy znacie jakieś inne profilery pamięci które działają na Windowsie z MinGW? Czy valgrind uruchomiony na wirtualce z Linuxem byłby szybszy?

9. Jakie są inne typowe "punkty zapalne" w kompletnie niezoptymalizowanym i totalnie beznadziejnym kodzie który jednak działa jak należy, oprócz sposobu przechowywania zasobów?

10. Jakieś inne porady?



Dodam od razu że za swój cel obrałem redukcję zużycia RAMu o połowę - tj. max. 500MB.

Z góry dziękuję za wszelką pomoc i pozdrawiam.

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Xion

  • Redaktor
    • xion.log

# Kwiecień 13, 2014, 17:51:22
Jakiego rzędu jest przeciętna ilość obiektów, które istnieją w jednym momencie? Zastanawiam się, na ile overhead alokacji może wpływać na zużycie pamięci. Jeśli nie są to dziesiątki czy setki tysięcy, zapewne lepiej skoncentrować się na innych potencjalnych zyskach (leniwe ładowanie zasobów wygląda obiecująco).

Offline laggyluk

  • Użytkownik
    • http://laggyluk.com

# Kwiecień 13, 2014, 18:06:09
można policzyć ile pamięci zajmuje rozpakowana grafika znając jej rozmiar i paletę. obstawiam że tu tkwi problem, dźwięki też potrafią dużo zajmować

Offline lethern

  • Użytkownik

# Kwiecień 13, 2014, 18:24:22
Sorry za prymitywną odpowiedź, ale... może zacząć trzymać grafikę w VRAM? Choć nie znam SDL pod spodem i nie wiem czy to umożliwia, może to jakaś grubsza przesiadka w kodzie
Bo choćbyś miał 10000 wskaźników, to nadal to jest rząd kB

Co do animacji, możesz sprawdzić tak oczywiste rzeczy, jak to czy zdekompresowana jest tylko aktualna klatka czy całość - na zdrowy rozum, gra powinna robić "cache", czyli "bierz ramu ile jest, a jak nie ma to czyść cache i obliczaj na bierząco" ;p
« Ostatnia zmiana: Kwiecień 13, 2014, 18:30:18 wysłana przez lethern »

Offline Xirdus

  • Redaktor

# Kwiecień 13, 2014, 19:04:46
Jakiego rzędu jest przeciętna ilość obiektów, które istnieją w jednym momencie? Zastanawiam się, na ile overhead alokacji może wpływać na zużycie pamięci. Jeśli nie są to dziesiątki czy setki tysięcy, zapewne lepiej skoncentrować się na innych potencjalnych zyskach (leniwe ładowanie zasobów wygląda obiecująco).
No właśnie "naprawa" tych wszystkich new to będzie ostatnia deska ratunku. Z tym że dziesiątki tysięcy alokacji są spokojnie, setki tysięcy myślę że tak, nie zdziwiłbym się gdyby to były miliony (mówię o wywołaniach new przy ładowaniu zasobów - w trakcie gry raczej dynamiczna alokacja nie występuje w znacznym stopniu). Zaraz się pobawię w przeciążanie new i podam dokładne liczby.

można policzyć ile pamięci zajmuje rozpakowana grafika znając jej rozmiar i paletę. obstawiam że tu tkwi problem, dźwięki też potrafią dużo zajmować
Grafika ma format R8G8B8A8 - ograniczenie palety nie wchodzi w grę, bo jakość ma się nie zmniejszyć. Po przekonwertowaniu wszystkich plików z PNG na BMP zajmują one łącznie 1.77GB - po dodaniu alfy daje to około 2.36GB, a ponieważ w danej scenie używane jest tak pi razy oko najwyżej 20% wszystkich assetów, daje to mniej więcej 500MB gołych danych (przeszacowane). Mowa tu o samych obrazach.

dźwięki też potrafią dużo zajmować
Co prawda dużą część gry stanowią mówione dialogi, ale one są ładowane na żądanie. Zostają wszelkie efekty dźwiękowe oraz muzyka tła - jednak tutaj przeprowadziłem mały eksperyment, i po wyrżnięciu z plików gry wszystkich plików dźwiękowych zużycie RAMu w najbardziej zasobożernej lokacji spadło "zaledwie" o 100MB, z 957 do 849. Na pewno się za to też wezmę, ale nie jest to priorytetem.

Sorry za prymitywną odpowiedź, ale... może zacząć trzymać grafikę w VRAM? Choć nie znam SDL pod spodem i nie wiem czy to umożliwia, może to jakaś grubsza przesiadka w kodzie
VRAM też jest ograniczony - czasami bardziej niż RAM ;)

Co do animacji, możesz sprawdzić tak oczywiste rzeczy, jak to czy zdekompresowana jest tylko aktualna klatka czy całość - na zdrowy rozum, gra powinna robić "cache", czyli "bierz ramu ile jest, a jak nie ma to czyść cache i obliczaj na bierząco" ;p
Sprawdziłem kod i SDL_Texture jest tworzony na czas wyświetlania klatki i niszczony przy następnej klatce. Za to plik z filmem siedzi cały czas w pamięci (ale to tylko kilka mega, a wszystkich filmów jest ze dwadzieścia). Nie wiem natomiast, co się dzieje wewnątrz samego libavcodec.



Myślę że zacznę kombinowanie od zamiany czytania z ZIPa na czytanie plików z normalnej struktury katalogów.

Offline rafal3920

  • Użytkownik

  • +1
# Kwiecień 13, 2014, 20:15:53
Tło jest ruchome czy statyczne? Jeśli ruchome to możnaby podzielić na kilka części które będą wczytywane tuż przed wyświetleniem.

Offline steckel

  • Użytkownik

# Kwiecień 13, 2014, 20:54:03
2. Gra posiada bardzo dużo unikalnych obiektów, a lokacje to przede wszystkim wielkie obrazy tła. Ze względów wydajnościowych wszystkie elementy potrzebne w danej scenie gra preloaduje na wejściu, mimo że nie wszystkie są naraz używane. Czy istnieje jakiś bardziej efektywny sposób trzymania obrazów w pamięci niż prosta bitmapa, taki, który nie wpłynie na wydajność ani nie pogorszy jakości grafiki (jakość grafiki to główny punkt marketingu)?

3. Gra korzysta z SDL2 oraz SDL2_img, każdy spritesheet trzyma w osobnym SDL_Texture. Czy przez jakieś bardziej optymalne ułożenie sprite'ów dałoby się zmniejszyć zużycie pamięci (nie wiem, np. wspólny atlas tekstur czy bardziej kwadratowe ułożenie elementów)?

Oczywiście, że bardziej optymalne ułożenie zmniejszyłoby zużycie pamięci. Można do tego wykorzystać program TexturePacker: http://www.codeandweb.com/texturepacker

Dzięki temu otrzymasz atlasy POT (PowerOfTwo np. 2048x2048) i możesz trzymać je skompresowane w pamięci poprzez kompresję PVRTC, która jest najefektywniejsza.
http://blog.imgtec.com/powervr/pvrtc-the-most-efficient-texture-compression-standard-for-the-mobile-graphics-world
https://sites.google.com/site/richgel99/early-pvrtc-compression-experiments

Gdy chcesz używać grafiki, to ją rozkompresowujesz i wyświetlasz, a potem niszczysz ją, gdy już jej nie potrzebujesz. Oczywiście wpłynie to na wydajność, ale jeżeli gra wciąż będzie się utrzymywać powyżej 60 fps to warto.

Offline kalicjusz

  • Użytkownik
    • Kidler3D

# Kwiecień 13, 2014, 21:31:51
Można użyć również skompresowanej grafiki i plików DDS (format DXT3
lub DXT5). Zredukuje to pamięć i zwiększy fps. Jeśli chodzi o jakość to można dodać opcje w menu z ustawieniami grafiki: „używaj skompresowanych tekstur” (.dds) lub „nie używaj skompresowanych tekstur” (.bmp/.png).

Offline Liosan

  • Redaktor

  • +1
# Kwiecień 13, 2014, 21:40:04
Ło rany, ale kombinujecie z kompresowaniem tekstur w pamięci. Najmniej miejsca zajmują tekstury których się nie wczytuje :P

2. Gra posiada bardzo dużo unikalnych obiektów, a lokacje to przede wszystkim wielkie obrazy tła. Ze względów wydajnościowych wszystkie elementy potrzebne w danej scenie gra preloaduje na wejściu, mimo że nie wszystkie są naraz używane. Czy istnieje jakiś bardziej efektywny sposób trzymania obrazów w pamięci niż prosta bitmapa, taki, który nie wpłynie na wydajność ani nie pogorszy jakości grafiki (jakość grafiki to główny punkt marketingu)?

7. Gra trzyma zasoby w zipie, do wczytywania plików używa biblioteki miniz. Ma ktoś z nią doświadczenie? Czy po otworzeniu archiwum przez mz_zip_reader_init_file() przechowuje ona dużo rzeczy w pamięci?
Wydaje mi się, że te problemy są powiązane. Tzn duży plik .zip jest dekompresowany w całości i wczytywany naraz do pamięci, i tak zostaje. Gry AAA (jak dajmy na to Wiedźmin 2 z "dużym zipem" wielkości 10 GB) radzą sobie z tym tak, że ten plik to nie jest jeden zip - tylko każdy mały plik w tym VFSie jest zipowany oddzielnie, i całość jest konkatenowana. Więc nadal mamy korzyści z czytania hurtowego setek małych plików, a zyskujemy możliwość random access do tego pliku. Takie podejście, myślę, sprawdziłoby się najlepiej - inteligentne zarządzanie tymi obrazami tła, m. in. preloading i sprzątanie na koniec.

8. Z powodu niebotycznej ilości new i delete, Dr. Memory działa tak wolno, że tracę cierpliwość zanim nawet się włączy menu. Czy znacie jakieś inne profilery pamięci które działają na Windowsie z MinGW? Czy valgrind uruchomiony na wirtualce z Linuxem byłby szybszy?
valgrind nie do tego służy. Potrzebujesz czegoś w stylu Google Perf Tools które pozwalają na profilowanie żywej pamięci względem miejsca gdzie została zalokowana. Tzn - dostaniesz tabelkę, która linia z `new` odpowiada za najwięcej żywej alokowanej pamięci. Ilość new/delete Cię zupełnie nie interesuje - ważna jest używana pamięć.

Liosan

Offline ackg

  • Użytkownik

# Kwiecień 13, 2014, 22:09:39
Jeśli tekstury nie mieszczą się w VRAM, to sterownik przerzuca to co aktualnie nieużywane do RAMu. Więc może powstać sytuacja, że tekstura jest w trzech kopiach: w VRAM, w RAM zaalokowanym przez driver GPU, i w RAM wczytana przez samą grę. Więc jeśli konstrukcja gry na to pozwala, dobrze byłoby usuwać tekstury z RAMu po wczytaniu ich do VRAMu.

Jeśli gra przełącza się między scenami, tj. sceny są oddzielne, bez płynnego przejścia, można by przed przełączeniem usunąć niepotrzebne i doczytać brakujące zasoby, skoro gra i tak doczytuje w trakcie działania (dialogi). Zależnie od tego jak gra jest skonstruowana dodanie do scen list potrzebnych zasobów może być teraz skomplikowane.

Narzut miniz jest pomijalny, 32kB czy coś takiego. No chyba, że pliki po wczytaniu i przetworzeniu dalej pałętają się po pamięci, ale to już niezależne od miniz.

Użycie atlasów tekstur nie zmniejszy zużycia pamięci, raczej w drugą stronę. Co najwyżej mogą zwiększyć wydajność, ale nie tutaj chyba leży problem.

Offline steckel

  • Użytkownik

# Kwiecień 13, 2014, 22:13:49
Użycie atlasów tekstur nie zmniejszy zużycia pamięci, raczej w drugą stronę. Co najwyżej mogą zwiększyć wydajność, ale nie tutaj chyba leży problem.

Samo użycie atlasów nie zmniejszy użycia pamięci, ale dzięki nim dostajemy tekstury POT, które możemy kompresować za pomocą PVRTC.

Offline ackg

  • Użytkownik

# Kwiecień 13, 2014, 22:26:08
PVRTC to kompresja stratna, a podane jest założenie, że jakość nie może się pogorszyć.

Offline steckel

  • Użytkownik

# Kwiecień 13, 2014, 23:00:57
Racja, trzeba by było sprawdzić jak bardzo pogarsza to jakość.

Można by też spróbować kompresji przy zwiększonej rozdzielczości grafik.
« Ostatnia zmiana: Kwiecień 13, 2014, 23:02:46 wysłana przez steckel »

Offline Super Vegeta

  • Użytkownik
    • SV Games

# Kwiecień 14, 2014, 00:05:22
Co do profilowania - gprof? Tam co prawda dane masz nie na bieżąco, a dopiero po wyjściu z programu, ale jeśli byłbyś w stanie stworzyć jakiś zautomatyzowany test, to jest bardzo przydatne. Parę razy już udało mnie się znaleźć dzięki niemu bottlenecki.

Offline ArekBal

  • Użytkownik

# Kwiecień 14, 2014, 01:17:19
atlasy mogą pozwolić ciaśniej upchać spritey (zalezy jakie)