1 / 20

Programowanie gier komputerowych Tomasz Martyn

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ż

nasnan
Download Presentation

Programowanie gier komputerowych Tomasz Martyn

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Programowanie gier komputerowychTomasz Martyn Wykład 2. Zarządzanie pamięcią Start i zamykanie systemu

  2. Dynamiczne przydziały i zwalnianie pamięci (standardowe implementacje malloc, new, free, delete) na stercie (heap-basedallocation): • 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 • prowadzą do fragmentacji pamięci Zarządzanie pamięcią (1)Po co?

  3. Ogólne dyrektywy: • Nie przydzielać dynamicznie pamięci przy wykorzystaniu standardowego heap-basedallocation w toku rozgrywki • 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ą • (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ą Zarządzanie pamięcią (2) No i co z tym zrobić?

  4. (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) Zarządzanie pamięcią (3) Naturalne położenie danych atomowych

  5. sizeof(S2) = 8 sizeof(S1) = 12 Zarządzanie pamięcią (4) Wyrównanie struktur (padding) (x86, Visual C++ 2010)

  6. (one-ended)stackallocator • double-endedstackallocator Zarządzanie pamięcią (5) Alokacja na stosie (1) 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.

  7. (patrz np.: S. Ranck: Alokacja oparta na ramkach, w: Perełki programowania gier 1) Zarządzanie pamięcią (6) Alokacja na stosie (2) Klasa

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

  9. Zarządzanie pamięcią (8) Alokacja na stosie (4)Przydział i zwalnianie pamięci

  10. 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.) Zarządzanie pamięcią (9) Alokacja oparta na puli pamięci (1) 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)

  11. (patrz: P. Glinker: Fightmemoryfragmentation with templatedfreelists, w: Game Programming Gems 4; również: N. Mefford: Improvingfreelists with policy based design, w Game Programming Gems 5) Zarządzanie pamięcią (10) Alokacja oparta na puli pamięci (2)Klasa

  12. Zarządzanie pamięcią (11) Alokacja oparta na puli pamięci (3)Konstrukcja i destrukcja

  13. NewInstance() { Zarządzanie pamięcią (12) Alokacja oparta na puli pamięci (4)Przydział i zwalnianie pamięci

  14. 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 heapallocator, 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) Zarządzanie pamięcią (13) Alokacja na własnej stercie • 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

  15. (patrz np.: P. Isensee: Alokatory STL, w: Perełki programowania gier 3) • 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 alokatoraz 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ć: Zarządzanie pamięcią (14) Alokatory STL (1) • 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

  16. Choć większość implementacji STL spełnia wymagania standardu ANSI C++, to jednak same implementacje różnią się od siebie. • W rezultacie własny alokator zdefiniowany w kontekście danej implementacji może nie działać w innej. • 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). • 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 ElectronicArts) Zarządzanie pamięcią (15) Alokatory STL (2)

  17. 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. • 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. • „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. {} Start i zamykanie systemu (1)

  18. (patrz: S. Bilas: Automatyczne singletony, w: Perełki programowania gier 1) • 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: Start i zamykanie systemu (2)

  19. 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. Start i zamykanie systemu (3)

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

More Related