Wzorce alokacji pamięci wykorzystywane przy tworzeniu gier

20

Badam tworzenie własnych metod alokacji (które będą wspierać takie rzeczy, jak pula pamięci i profilowanie), jednak w miarę kontynuowania badań szukałem sposobu, w jaki to zostało zrobione w rozwoju gier.

Jakiej techniki alokacji pamięci mogę użyć i dlaczego jest to dobra technika?

czad
źródło
1
czy naprawdę potrzebujesz? to tylko jedna z najbardziej skomplikowanych rzeczy, jakie zespół może kiedykolwiek wdrożyć, jeśli potrafią to wdrożyć.
Ali1S232,
4
Jest to dla mnie dziedzina zainteresowań, dlatego chciałbym się o tym dowiedzieć i wdrożyć
chadb,
Muszę powiedzieć, że temat jest naprawdę interesujący ... Są przypadki, w których może to oznaczać przydział, ale na twojej przeciętnej grze na PC wolałbym martwić się o rzeczywistą grę ...
rioki
Czy szukałeś kodu źródłowego dla nowoczesnego standardowego biblioteki malloc oraz darmowego lub nowego i usuwanego? Pytam, ponieważ wydaje się, że stanowiłoby to bardzo przydatną podstawę do porównania alternatywnych strategii alokacji z algorytmicznym lub praktycznym. Wydaje się, że zapewniłoby to również prawdziwy wgląd w to, w co się pakujesz.
Louis Langholtz

Odpowiedzi:

25

Architektura silnika gry zawiera informacje na ten temat. Podstawą jest to, że musisz przeprowadzić analizę, aby zrozumieć, jakie są wymagania dotyczące pamięci na poziom / ramkę / itp. są podobne, ale istnieje kilka wzorów, o których autor wspomniał kilkakrotnie:

  • Alokatory oparte na stosie: alokują duży segment pamięci jeden raz, a następnie przydzielają wskaźniki w tym bloku pamięci w odpowiedzi na żądania z innego miejsca w grze. Jest to przydatne, aby uniknąć przełączania kontekstu wymaganego przez alokację pamięci, a także dlatego, że można użyć własnych technik w celu wymuszenia ciągłości lub specyficznego wyrównania operacji SIMD. Niektóre silniki używają również podwójnego stosu, w którym jeden rodzaj zasobu jest ładowany od góry, a drugi od dołu. Być może LSR (Load and Stay Resident, rodzaj rzeczy, które będą potrzebne w całej grze) od góry, a dane na poziomie od dołu.
  • Pamięć pojedynczej klatki lub podwójnie buforowana pamięć klatki: Pamięć operacji wykonywanych w ciągu jednego lub dwóch cykli klatek. Jest to przydatne, ponieważ zamiast konieczności przydzielania / zwalniania każdej klatki, możesz po prostu zdmuchnąć dane ostatniej klatki, resetując wskaźnik, którego używasz do śledzenia pamięci na początku bloku.
  • Pule obiektów: Blok pamięci dla wielu obiektów tej samej wielkości, takich jak cząstki, wrogowie, pociski. Są one przydatne, ponieważ można łatwo uzyskać ciągłość, znajdując pierwszy nieużywany segment w puli. Ułatwiają także iterację, ponieważ każdy obiekt znajduje się w znanym przesunięciu względem ostatniego.

Najważniejszą rzeczą, o której wspomina autor, jest fragmentacja pamięci. Jest to mniejszy problem, jeśli pracujesz np. Na komputerze PC, na którym masz jakąś kopię zapasową stronicowania pamięci, na którą możesz liczyć, ale w stałym kontekście pamięci, takim jak konsola, istnieje ryzyko „braku pamięci” podczas próby przydzielenia dużego obiektu, ponieważ pamięć jest pofragmentowana w taki sposób, że dostępne są tylko małe ciągłe bloki. W tym celu zaleca, aby alokator oparty na stosie, jak powyżej, zawierał również metodę okresowej defragmentacji jego zawartości.

Aby uzyskać więcej informacji na temat obecnego kodu, bardzo polecam artykuł Christiana Gyrlinga: „Czy brakuje nam pamięci?” , która obejmuje techniki niestandardowych alokatorów, głównie z perspektywy analizy wzorców wykorzystania pamięci, ale dotyczy to również opracowania niestandardowego rozwiązania do zarządzania pamięcią.


źródło
1

Z tego, co widziałem (ale nie zrobiłem), każda gra ma tendencję do dziedziczenia mechanizmów alokacji z frameworka, z silnika gry, z poprzedniej wersji (2010 -> 2011) lub dostaje zestaw nowych napisanych specjalnie dla niego struktura (gdy struktury danych są wielokrotnego użytku i mają stały rozmiar, lub wiele typów i zmiennych rozmiarów).

Mieliśmy także inne alokatory dla plików dźwiękowych / komponentów niż dla poziomów i innych obiektów gry w tym samym projekcie. W innych projektach alokatory są dziedziczone z bibliotek zewnętrznych tylko dla komponentów zarządzanych przez tę bibliotekę.

Optymalizacja naprawdę zależy od twoich potrzeb. Ale zwykle przydzielanie odbywa się przed wejściem na scenę gry, a następnie pamięć jest ponownie wykorzystywana. W niektórych grach można uniknąć niestandardowych przydziałów. Ale w przypadku gier akcji, w których budżetowane są zasoby procesora, pamięci i danych, nie możesz sobie pozwolić na stratę czasu przetwarzania przy dużych alokacjach, nie możesz marnować pamięci na fragmentację i inne problemy.

Jeśli chodzi o przykłady, powinieneś zacząć od spojrzenia na silnik gry OGRE 3D , który ma kilka opcji konfiguracji alokatorów pamięci.

Kojot
źródło
0

Często popełnianym błędem jest pisanie własnych alokatorów, abyś miał większą kontrolę nad ilością pamięci wykorzystywanej przez każdy system i miał lepszą widoczność tego, co się dzieje. O wiele lepszym sposobem na osiągnięcie tego jest użycie profilera pamięci. Istnieje wiele profilerów pamięci, mój profiler MemPro jest jednym z przykładów. Jest to całkowicie nieinwazyjny sposób śledzenia zużycia pamięci i można go automatycznie podzielić na podsystemy za pomocą filtrów wieloznacznych callstack. Idealnie najlepiej, jeśli przydział i śledzenie pamięci są całkowicie oddzielne, mają one zupełnie inne wymagania.

Arbitralne dzielenie pamięci na pule często może być szkodliwe, ponieważ każda pula będzie miała narzut. Możesz skończyć zużywając znacznie więcej pamięci, niż potrzebujesz, nie zdając sobie z tego sprawy. Aby zmniejszyć marnotrawstwo, zawsze lepiej jest zebrać wszystko razem, luz jest następnie dzielony przez cały system.

Jedynymi powodami korzystania z niestandardowych alokatorów są wydajność procesora (głównie dla spójności pamięci podręcznej) i ograniczenie fragmentacji. Doskonałym tego przykładem jest układ cząstek. Chcesz, aby wszystkie cząstki przylegały do ​​siebie w pamięci i nie chcesz pieprzyć pamięci głównej dużą ilością krótkotrwałych przydziałów. Innym dobrym przykładem partycjonowania jest język skryptowy.

Jeśli chcesz przykład zamiany malloc ogólnego zastosowania, możesz rzucić okiem na mój alokator VMem . Został on wykorzystany w wielu dostarczonych grach AAA. Ma techniki, które ograniczają fragmentację i utrzymują niski poziom pamięci, co jest krytyczne dla gier konsolowych. Jest również bardzo szybki przy dużej rywalizacji o wątek. Moja witryna ma obszerną dokumentację na temat tych technik.

Stewart Lynch
źródło