Książka Game Coding Complete, czwarta edycja , rozdział 5 ( Inicjalizacja i zamknięcie gry ), sekcja Sprawdzanie pamięci zawiera ten interesujący przykładowy kod:
bool CheckMemory(const DWORDLONG physicalRAMNeeded, const DWORDLONG virtualRAMNeeded)
{
MEMORYSTATUSEX status;
GlobalMemoryStatusEx(&status);
if (status.ullTotalPhys < physicalRAMNeeded)
{
// you don’t have enough physical memory. Tell the player to go get a
// real computer and give this one to his mother.
GCC_ERROR("CheckMemory Failure: Not enough physical memory.");
return false;
}
// Check for enough free memory.
if (status.ullAvailVirtual < virtualRAMNeeded)
{
// you don’t have enough virtual memory available.
// Tell the player to shut down the copy of Visual Studio running in the
// background, or whatever seems to be sucking the memory dry.
GCC_ERROR("CheckMemory Failure: Not enough virtual memory.");
return false;
}
char *buff = GCC_NEW char[virtualRAMNeeded];
if (buff)
{
delete[] buff;
}
else
{
// even though there is enough memory, it isn't available in one
// block, which can be critical for games that manage their own memory
GCC_ERROR("CheckMemory Failure: Not enough contiguous memory.");
return false;
}
}
Rodzi to kilka pytań.
Pierwsza część pyta tylko system operacyjny (Windows), ile fizycznej pamięci RAM jest dostępne. Ciekawą częścią jest druga, która przydziela ogromną część pamięci i od razu ją uwalnia:
char *buff = GCC_NEW char[virtualRAMNeeded];
if (buff)
{
delete[] buff;
}
Autor wyjaśnia dalej:
... ta funkcja przydziela i natychmiast uwalnia ogromny blok pamięci. Powoduje to, że system Windows usuwa wszelkie śmieci nagromadzone w menedżerze pamięci i dwukrotnie sprawdza, czy można przydzielić ciągły blok tak duży, jak potrzebujesz. Jeśli połączenie się powiedzie, w zasadzie uruchamiasz odpowiednik maszyny Zamboni w pamięci systemu, przygotowując ją do uderzenia w lód ...
Ale mam co do tego zastrzeżenia.
„Czyścisz śmieci, które nagromadziły się w menedżerze pamięci?” Naprawdę? Jeśli gra właśnie się rozpoczęła, czy nie powinno być śmieci?
„Upewnij się, że możesz przydzielić ciągły blok?” W bardzo szczególnym przypadku, w którym zamierzasz samodzielnie zarządzać pamięcią, miałoby to jakiś sens, ale mimo to, jeśli przydzielisz dużo pamięci bezpośrednio od nietoperza, praktycznie uniemożliwisz uruchomienie innej aplikacji system, gdy twój jest włączony.
Ponadto, czy nie zmusi to systemu operacyjnego do zatwierdzenia całej pamięci, aw konsekwencji spowoduje wyrzucenie dużej ilości pamięci na miejsce na dysku wymiany, co spowalnia uruchomienie aplikacji?
Czy to naprawdę dobra praktyka?
operator new
dlanullptr
), jeśli pozwolisz mi powiedzieć. Najlepszą rzeczą, jaką możesz zrobić z tą książką, jest rozpalenie komina. Przydzielanie i zwalnianie dużego bloku pamięci oczywiście nie „oczyszcza” pamięci.new
operatora, aby zwrócił wartość zero zamiast rzucaćbad_alloc
. Jeśli nie, to tak, ten kod jest jeszcze bardziej bezsensowny: Poperator delete
jest , należy zaakceptowaćnullptr
i potraktować to jako brak możliwości. Każde globalne przeciążenie, które tego nie robi, jest zepsute. Co oznacza, że jest to nonsensowne w obu przypadkach. Podobnie jak założenie, że przydzielenie ogromnego bloku pamięci i zwolnienie go „magicznie” zrobi coś dobrego. W najlepszym wypadku nie zaszkodzi (najprawdopodobniej, ponieważ strony nie są nawet dotykane ... w przeciwnym razie może zamienić niektóre strony z zestawu roboczego, które trzeba będzie ponownie załadować).Odpowiedzi:
Jedną z rzeczy, które można zrobić wstępnie przydzielając dużą część pamięci , jeśli na komputerze jest mało pamięci RAM, może być wymuszenie zwolnienia przez system operacyjny dodatkowej przestrzeni przez zamianę części pamięci innych programów na dysk.
Ponieważ taka zamiana jest na ogół bardzo powolną operacją, która praktycznie zawiesza Twój program podczas jego trwania, pewną zaletą może być zapewnienie, że jeśli tak się stanie, nastąpi to przed rozpoczęciem gry, a nie w trakcie rozgrywki.
To powiedziawszy, zmuszanie takiej zamiany na dysk nie jest w 100% niezawodne:
W wielu implementacjach pamięci wirtualnej samo przydzielenie pamięci tak naprawdę nie powoduje wymiany; dzieje się tak tylko wtedy, gdy po raz pierwszy uzyskujesz dostęp do każdej przydzielonej strony pamięci. (Jest to z pewnością prawdziwe w nowoczesnych wersjach systemu Linux, z włączoną funkcją nadpisywania pamięci, ponieważ jest ona domyślnie; nie wiem na pewno, w jaki sposób obsługują ją różne wersje systemu Windows).
Tak więc, jeśli naprawdę chcesz, aby ten kod „wyczyścił pamięć”, prawdopodobnie powinieneś dodać
memset()
wywołanie lub odpowiednik, aby faktycznie zapisać dane do każdej części tablicy (lub przynajmniej do jej pierwszychphysicalRAMNeeded
bajtów) przed zwolnieniem.Ponadto, zmuszając OS aby zamienić innych programów z pamięci RAM jak to nie znaczy, że będą one pozostać z niego. Windows to system wielozadaniowy, a gdy tylko jeden z tych zamienionych programów chce ponownie uruchomić i użyć swoich zamienionych danych, zostanie ponownie włączony, potencjalnie ponownie zamrażając grę.
Dlatego ta sztuczka jest na ogół użyteczna tylko wtedy, gdy pamięć RAM jest pochłaniana przez duże porcje danych, do których nie ma aktywnego dostępu (takie jak duże dokumenty pozostawione otwarte w tle w niektórych programach do edycji). Przeglądarki internetowe są pod tym względem szczególnie problematyczne, ponieważ nawet jeśli nie używasz strony internetowej otwartej w zakładce w tle, strona może nadal mieć uruchomione skrypty, które zmuszają ją do przechowywania w pamięci RAM.
(Nie są sposoby, aby program mógł właściwie powiedzieć OS zablokować część swojej przestrzeni pamięci do pamięci RAM i zapobiec jego wyswapowany, ale te wymagają zwykle podwyższonymi uprawnieniami. W każdym razie, kod zostanie zaksięgowana nie wydaje się być przy użyciu takich metod).
Zasadniczo ta sztuczka wydaje się przydatna tylko w tej stosunkowo wąskiej sytuacji na granicy, w której użytkownik miałby wystarczającą ilość pamięci RAM, aby uruchomić grę (i każde inne oprogramowanie aktywnie działające w tle) bez zamiany, ale ta pamięć RAM jest obecnie wypełniona nieaktywnymi i nieużywanymi otwartymi plikami lub inne dane, które można i należy zamienić na dysk podczas działania gry. W takich sytuacjach może sprawić, że Twoja gra będzie wydawać się płynniejsza i bardziej responsywna, zastępując sporadyczne małe operacje wymiany podczas gry na jedną podczas uruchamiania.
źródło
Nie wiem, ile lat ma ten zapis, ale powiedziałbym, że jest dość stary. W nowoczesnym systemie Windows (XP i nowszych, ale szczególnie w wersjach 64-bitowych) powiedziałbym, że zrobienie czegoś takiego będzie miało niewielki lub negatywny wpływ na uruchomienie twojej gry.
Przede wszystkim pamiętaj, że przestrzeń adresowa jest wirtualizowana dla każdego procesu. Jeśli chodzi o proces, masz całą przestrzeń adresową dla siebie i zawsze otrzymasz ciągłą (wirtualną) pamięć, niezależnie od tego, czy ta pamięć jest fizycznie ciągła, czy nie.
W rzeczywistości to, czy pamięć jest ciągła w pamięci fizycznej, nie jest czymś, nad czym masz kontrolę. System operacyjny wybierze sposób alokacji fizycznych bloków według własnego uznania i nic, co zrobisz na poziomie aplikacji, nie może mieć wpływu na korzyści (jeśli w ogóle) wynikające z posiadania ciągłej pamięci fizycznej.
Musisz dbać o fragmentację pamięci wirtualnej, jeśli przez bardzo długi czas przydzielasz i zwalniasz pamięć o różnych rozmiarach. Jest to oczywiście znacznie mniej istotne w przypadku 64-bitowej przestrzeni adresowej. Gry zwykle nie działają przez miesiące, więc najprawdopodobniej nie będziesz musiał się tym przejmować, chyba że mówimy o długo działających serwerach gier.
Nawet jeśli przydzielasz dużo pamięci o bardzo różnych rozmiarach na maszynie 32-bitowej, nowoczesne przydziały pamięci są całkiem dobre, więc jest mało prawdopodobne, że wystąpią problemy z fragmentacją pamięci wirtualnej, chyba że aktywnie ich szukasz
Szczerze mówiąc, nie wiem, o co chodzi z tym pisarzem. Nawet jeśli przestrzeń adresowa była współdzielona i otrzymałeś ogromny ciągły blok w pamięci fizycznej, uwalniając ją, mówisz, że już cię to nie obchodzi. Istnieją inne programy działające w tle, które dokonują własnych alokacji, więc nawet jeśli twój wielki malloc odniesie sukces, nikt nie zagwarantuje, że następny również odniesie sukces.
Myślę, że to przypadek „zadziałało z jakiegoś powodu, którego tak naprawdę nie rozumiem, więc wymyśliłem coś, żeby to wyjaśnić”.
źródło