Programowanie gier komputerowych Tomasz Martyn

20
Wykład 2. Zarządzanie pamięcią Start i zamykanie systemu Programowanie gier komputerowych Tomasz Martyn

description

Programowanie gier komputerowych Tomasz Martyn. Wykład 2. Zarządzanie pamięcią Start i zamykanie systemu. Dynamiczne przydziały i zwalnianie pamięci (standardowe implementacje malloc , new , free , delete ) na stercie ( heap-based allocation ): są czasochłonne, ponieważ - PowerPoint PPT Presentation

Transcript of Programowanie gier komputerowych Tomasz Martyn

Page 1: Programowanie gier komputerowych Tomasz Martyn

Wykład 2. Zarządzanie pamięcią

Start i zamykanie systemu

Programowanie gier komputerowychTomasz Martyn

Page 2: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (1) Po co?

Dynamiczne przydziały i zwalnianie pamięci (standardowe implementacje malloc, new, free, delete) na stercie (heap-based allocation):1. są czasochłonne, ponieważ

- jest to system ogólnego przeznaczenia, który musi alokować pamięć dowolnej wielkości i w dowolnej chwili, a zatem, aby to zapewnić, w trakcie alokacji muszą być wykorzystywane rożne algorytmy i techniki, (poszukiwanie wolnego obszaru pamięci, pamięć wirtualna, defragmentacja,...)

- wymaga odwołania do systemu operacyjnego (kosztowne przełączenie kontekstu procesora)

- prowadzą do niekontrolowanego rozproszenia danych w pamięci (tzw. słaba lokalność odwołań – locality of reference), a to z kolei skutkuje chybianiem w pamięć cache procesora (cache misses) podczas operacji na pamięci

2. prowadzą do fragmentacji pamięci

Page 3: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (2) No i co z tym zrobić?

Ogólne dyrektywy:1. Nie przydzielać dynamicznie pamięci przy wykorzystaniu

standardowego heap-based allocation w toku rozgrywki2. Przydzielać pamięć z ciągłego bloku, prealokowanego w trakcie

inicjalizacji gry (być może na stercie lub w pamięci statycznej) przy wykorzystaniu własnej, odpowiedniej strategii zarządzania pamięcią

3. (w celu minimalizacji chybiania w cache) Zorganizować dane w ciągłe i jak najmniejsze kawałki pamięci oraz posortować te dane tak, żeby ich odczyty były jak najbardziej sekwencyjne

Jednakże Należy pamiętać o odpowiednim wyrównaniu danych atomowych w pamięci (data alignment), tzn. adres danej w pamięci powinien być wielokrotnością rozmiaru danej (typowo potęga 2).Kontroler pamięci procesora działa wówczas wydajnie (odczyt tylko jednego bloku pamięci, zamiast dwóch lub więcej). Co więcej, niektóre procesory w ogóle nie potrafią odczytywać „niewyrównanych” danych (np. większość procesorów RISC, AltiVec) – x86 potrafią

Page 4: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (3) Naturalne położenie danych atomowych

(w nawiasach rozmiary typów dla 32-bitowego x86)• dana 1-bajtowa (char) może znajdować się pod dowolnym

adresem (8-bit alignment)• dana 2-bajtowa (short) powinna znajdować się pod adresem

parzystym (16-bit alignement)• dana 4-bajtowa (int, long, float, wskaźnik) powinna

znajdować się pod adresem będącym wielokrotnością 4 (32-bit alignment)• dana 8-bajtowa (double) powinna znajdować się pod

adresem będącym wielokrotnością 8 (Windows – 64-bit alignment) albo 4 (Linux – 32-bit alignment)• dana 16-bajtowa (wektor 4 floatów SEE) musi znajdować

się pod adresem będącym wielokrotnością 16 (128-bit alignment)

Page 5: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (4) Wyrównanie struktur (padding)

sizeof(S1) = 12 sizeof(S2) = 8

(x86, Visual C++ 2010)

Page 6: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (5) Alokacja na stosie (1)

• (one-ended) stack allocator

• double-ended stack allocator

Używane zwykle gdy gra jest liniowa i „zorientowana na poziomy” (tzn. gracz czeka na załadowanie poziomu, następnie przechodzi poziom, następnie czeka na załadowanie kolejnego poziomu, itd.) oraz każdy poziom mieści się całkowicie w pamięci.

Page 7: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (6) Alokacja na stosie (2)

Klasa(patrz np.: S. Ranck: Alokacja oparta na ramkach, w: Perełki programowania gier 1)

Page 8: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (7) Alokacja na stosie (3)Konstrukcja i destrukcja

Page 9: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (8) Alokacja na stosie (4)

Przydział i zwalnianie pamięci

Page 10: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (9) Alokacja oparta na puli pamięci (1)

Cechy:• udostępnia bloki pamięci równych rozmiarów• prealokowany duży, ciągły obszar pamięci będący wielokrotnością rozmiaru

przechowywanych obiektów (po kompilacji, tzn. w sensie sizeof – padding!)Działanie:• wskaźniki do niezajętych bloków pamięci przechowywane są w tablicy wolnych elementów

(podczas inicjalizacji tablica przechowuje wskaźniki na wszystkie bloki w przydzielonym obszarze pamięci)

• tablice wolnych elementów można również zakodować bezpośrednio w wolnych blokach przechowując w tych blokach wskaźniki na kolejny wolny element (przy założeniu, że wielkość bloków jest nie mniejsza od rozmiaru wskaźnika)

• przydział pamięci odbywa się poprzez pobranie ostatniego wskaźnika bloku z tablicy wolnych elementów i dekrementacji markera pamietającego indeks ostatniego wskaźnika w tablicy

• zwolnienie bloku odbywa się poprzez dołączenie wskaźnika na ten blok do tablicy wolnych elementów i inkrementacje markera (j.w.)Zastosowanie:

• do przechowywania wielu (zwykle niewielkich) obiektów tego samego typu (np. macierzy, węzłów drzewa, instancji siatki trójkątów...)

• do przechowywania zasobów, które można podzielić na kawałki (zwykle stosunkowo duże)

Page 11: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (10) Alokacja oparta na puli pamięci (2)

Klasa(patrz: P. Glinker: Fight memory fragmentation with templated freelists, w: Game Programming Gems 4;również: N. Mefford: Improving freelists with policy based design, w Game Programming Gems 5)

Page 12: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (11) Alokacja oparta na puli pamięci (3)

Konstrukcja i destrukcja

Page 13: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (12) Alokacja oparta na puli pamięci (4)

Przydział i zwalnianie pamięci

NewInstance() {

Page 14: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (13) Alokacja na własnej stercie

• można zaimplementować swój własny system zarządzania pamięcią bazujący na strukturze sterty (lub podobnej – por. np.: D. Lazarov: High performance heap allocator, w: Game Programming Gems 7)

• system może wykonywać częściową defragmentację w kolejnych iteracjach pętli gry bez widocznego wpływu na szybkość działania gry, przy założeniu, że przesuwane bloki zajętej pamięci nie są za duże (co zwykle jest spełnione, jeśli pamięć w ten sposób przydzielana jest dla dynamicznych obiektów gry, które na ogół są relatywnie niewielkie pod względem pamięciowym)

• jednakże dokonując defragmentacji należy aktualizować wskaźniki do przesuniętych bloków; z tego względu najlepiej stosować inteligentne wskaźniki, a jeszcze lepiej - uchwyty

Page 15: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (14) Alokatory STL (1)

• kontenery STL umożliwiają zdefiniowanie własnych strategii alokacji pamięci (alokatorów), np. definicja listy STL ma następującą postać:

• na ogół zdefiniowanie własnego alokatora sprowadza się do skopiowania domyślnego alokatora z pliku nagłówkowego <memory> i zastąpienie jego funkcji składowych allocate() i deallocate()(oraz ew. konstruktorów i destruktorów, rzadziej operatorów porównania)

• allocate() musi zwrócić wskaźnik na pamięć o rozmiarze wystarczającym do pomieszczenia n obiektów typu T (nie zajmuje się konstrukcją tych obiektów). Na przykład alokator dla prealokowanej pamięci wskazywanej przez mpStack może mieć postać:

(patrz np.: P. Isensee: Alokatory STL, w: Perełki programowania gier 3)

• deallocate(pointer p, size_type n) zwalnia pamięć wskazywaną przez p i zajmowaną przez n obiektów typu T; wskaźnik p musiał zostać wcześniej zwrócony przez allocate() tego samego obiektu alokatora; funkcja nie może zgłaszać wyjątku

Page 16: Programowanie gier komputerowych Tomasz Martyn

Zarządzanie pamięcią (15) Alokatory STL (2)

1. Choć większość implementacji STL spełnia wymagania standardu ANSI C++, to jednak same implementacje różnią się od siebie.

2. W rezultacie własny alokator zdefiniowany w kontekście danej implementacji może nie działać w innej.

3. W szczególności dotyczy to alokatorów zawierających zmienne składowe (własne dane - np. wskaźnik na prealokowany z zewnątrz blok pamięci albo statyczny blok pamięci wewnątrz alokatora).

4. Dlatego wiele wieloplatformowych silników gier dostarcza własne implementacje kontenerów. Praktyka ta jest również powszechna w silnikach na konsole i platformy mobilne. (Np. EA STL by Electronic Arts)

Page 17: Programowanie gier komputerowych Tomasz Martyn

Start i zamykanie systemu (1)1. Silnik gry jest złożonym systemem, składającym się z wielu oddziałujących ze sobą

podzespołów (modułów). Wzajemne zależności miedzy modułami znajdują m.in. swój wyraz w kolejności tworzenia modułów w trakcie uruchamiania systemu silnika, a także ich usuwania podczas zamykania tego systemu.

2. Wiele z modułów (w szczególności różne moduły zarządców) implementowanych jest przy wykorzystaniu wzorca projektowego singleton. Mając na uwadze pkt 1, istotne jest zatem, aby posiadać kontrolę nad kolejnością tworzenia i usuwania modułów-singletonów.

3. „Książkowy” singleton tworzony na żądanie implementowany jest na bazie wzorca:

Niestety rozwiązanie takie pozbawia nas kontroli nad kolejnością usuwania obiektów opartych na takiej implementacji.

{}

Page 18: Programowanie gier komputerowych Tomasz Martyn

Start i zamykanie systemu (2)

4. Lepsze rozwiązanie, które umożliwia tworzenie i usuwanie singletonów przy użyciu operatorów new i delete, opiera się na następującym wzorcu:

(patrz: S. Bilas: Automatyczne singletony, w: Perełki programowania gier 1)

Page 19: Programowanie gier komputerowych Tomasz Martyn

Start i zamykanie systemu (3)5. Wyizolowanie z implementacji klasy cechy „singleton” w formie odpowiednego wzorca

singletonu, po którym będą dziedziczyć klasy „singletonowe”, ma postać następującą:

Podejście takie stosowane jest np. w siniku Ogre3D.

Page 20: Programowanie gier komputerowych Tomasz Martyn

Start i zamykanie systemu (4)6. Przykład wykorzystania: