W poniższym przykładzie mam kilka powiązanych pytań dotyczących użycia pamięci.
Jeśli biegnę w tłumacza,
foo = ['bar' for _ in xrange(10000000)]
rzeczywista pamięć używana w moim komputerze wzrasta do
80.9mb
. Ja wtedydel foo
prawdziwa pamięć spada, ale tylko do
30.4mb
. Interpreter używa4.4mb
linii bazowej, więc jaka jest korzyść z nie zwalniania26mb
pamięci do systemu operacyjnego? Czy to dlatego, że Python „planuje z wyprzedzeniem”, myśląc, że możesz ponownie wykorzystać tyle pamięci?Dlaczego
50.5mb
w szczególności uwalnia - na jakiej podstawie jest uwalniana kwota?Czy istnieje sposób, aby zmusić Pythona do zwolnienia całej używanej pamięci (jeśli wiesz, że nie będziesz ponownie używać takiej ilości pamięci)?
UWAGA
To pytanie różni się od pytania Jak jawnie zwolnić pamięć w Pythonie?
ponieważ to pytanie dotyczy przede wszystkim zwiększenia użycia pamięci od linii bazowej, nawet po tym, jak interpreter zwolnił obiekty poprzez czyszczenie pamięci (z użyciem gc.collect
lub nie).
źródło
gc.collect
.Odpowiedzi:
Pamięć przydzielona na stercie może być narażona na znaczniki wysokiego poziomu. Jest to komplikowane przez wewnętrzne optymalizacje Pythona do przydzielania małych obiektów (
PyObject_Malloc
) w 4 pulach KiB, sklasyfikowanych pod względem rozmiarów alokacji jako wielokrotności 8 bajtów - do 256 bajtów (512 bajtów w 3.3). Same pule znajdują się na arenach 256 KiB, więc jeśli zostanie użyty tylko jeden blok w jednej puli, cała arena 256 KiB nie zostanie zwolniona. W Pythonie 3.3 alokator małych obiektów został przełączony na używanie anonimowych map pamięci zamiast sterty, więc powinien działać lepiej przy zwalnianiu pamięci.Ponadto typy wbudowane utrzymują wolne listy wcześniej przydzielonych obiektów, które mogą, ale nie muszą, używać alokatora małych obiektów.
int
Typ utrzymuje freelist z własnym przydzielonej pamięci i wyczyszczenie go wymaga wywoływaniaPyInt_ClearFreeList()
. Można to nazwać pośrednio, wykonując pełny plikgc.collect
.Spróbuj w ten sposób i powiedz mi, co otrzymujesz. Oto link do psutil.Process.memory_info .
Wynik:
Edytować:
Przerzuciłem się na pomiary w odniesieniu do rozmiaru maszyny wirtualnej procesu, aby wyeliminować wpływ innych procesów w systemie.
Środowisko wykonawcze C (np. Glibc, msvcrt) zmniejsza stertę, gdy ciągłe wolne miejsce na górze osiąga stały, dynamiczny lub konfigurowalny próg. Dzięki glibc możesz to dostroić za pomocą
mallopt
(M_TRIM_THRESHOLD). Biorąc to pod uwagę, nie jest zaskakujące, że sterta skurczy się bardziej - nawet znacznie bardziej - niż blok, który tyfree
.W 3.x
range
nie tworzy listy, więc powyższy test nie utworzy 10 milionówint
obiektów. Nawet jeśli tak,int
typ w 3.x to w zasadzie 2.xlong
, co nie implementuje listy wolnych.źródło
memory_info()
zamiastget_memory_info()
ix
jest zdefiniowanyint
s nawet w Pythonie 3, ale każda z nich zastępuje ostatnią zmienną w pętli, więc nie wszystkie istnieją naraz.Zgaduję, że pytanie, na którym naprawdę Ci zależy, to:
Nie, nie ma. Istnieje jednak łatwe obejście: procesy podrzędne.
Jeśli potrzebujesz 500 MB pamięci tymczasowej przez 5 minut, ale potem musisz pracować przez kolejne 2 godziny i nigdy więcej nie dotkniesz tak dużej ilości pamięci, stwórz proces potomny, aby wykonać pracę wymagającą dużej ilości pamięci. Kiedy proces dziecka odchodzi, pamięć zostaje uwolniona.
Nie jest to całkowicie trywialne i darmowe, ale jest dość łatwe i tanie, co zwykle jest wystarczająco dobre, aby handel był opłacalny.
Po pierwsze, najłatwiejszym sposobem utworzenia procesu potomnego jest użycie
concurrent.futures
(lub, w przypadku wersji 3.1 i wcześniejszych,futures
backport w PyPI):Jeśli potrzebujesz trochę większej kontroli, skorzystaj z
multiprocessing
modułu.Koszty są następujące:
mmap
ped lub w inny sposób; interfejsy API pamięci współdzielonej wmultiprocessing
; itp.).struct
lub najlepiej -ctypes
).źródło
eryksun odpowiedział na pytanie nr 1, a ja odpowiedziałem na pytanie nr 3 (oryginalne nr 4), ale teraz odpowiedzmy na pytanie nr 2:
Ostatecznie opiera się na całej serii zbiegów okoliczności w Pythonie,
malloc
które są bardzo trudne do przewidzenia.Po pierwsze, w zależności od tego, jak mierzysz pamięć, możesz mierzyć tylko strony faktycznie zmapowane w pamięci. W takim przypadku za każdym razem, gdy strona zostanie zamieniona przez pager, pamięć zostanie wyświetlona jako „zwolniona”, nawet jeśli nie została zwolniona.
Lub możesz mierzyć strony w użyciu, które mogą liczyć strony przydzielone, ale nigdy nie dotknięte (w systemach, które optymistycznie przydzielają zbyt dużo, takich jak linux), strony, które są przydzielone, ale oznaczone
MADV_FREE
itp.Jeśli naprawdę mierzysz przydzielone strony (co w rzeczywistości nie jest bardzo przydatne, ale wydaje się, że właśnie o to pytasz), a strony zostały naprawdę cofnięte, są to dwie okoliczności, w których może się to zdarzyć: albo użyłeś
brk
lub równoważnego do zmniejszenia segmentu danych (obecnie bardzo rzadko) lub użyłeśmunmap
lub podobnego do zwolnienia zmapowanego segmentu. (Teoretycznie istnieje również niewielki wariant tego ostatniego, ponieważ istnieją sposoby na uwolnienie części zmapowanego segmentu - np. Kradzież goMAP_FIXED
za pomocąMADV_FREE
segmentu, którego natychmiast usuwasz).Jednak większość programów nie alokuje bezpośrednio rzeczy ze stron pamięci; używają
malloc
alokatora w stylu. Kiedy wywołujeszfree
, alokator może zwolnić strony do systemu operacyjnego tylko wtedy, gdy akurat jesteśfree
ostatnim aktywnym obiektem w mapowaniu (lub na ostatnich N stronach segmentu danych). Nie ma możliwości, aby Twoja aplikacja mogła to przewidzieć, a nawet wykryć, że stało się to z wyprzedzeniem.CPython jeszcze bardziej komplikuje to zadanie - ma niestandardowy, dwupoziomowy alokator obiektów, znajdujący się na szczycie niestandardowego alokatora pamięci
malloc
. ( Bardziej szczegółowe wyjaśnienie można znaleźć w komentarzach do źródeł ). Ponadto, nawet na poziomie C API, a tym bardziej w Pythonie, nie można nawet bezpośrednio kontrolować, kiedy obiekty najwyższego poziomu są zwalniane.Tak więc, kiedy zwalniasz obiekt, skąd wiesz, czy zwolni on pamięć do systemu operacyjnego? Cóż, najpierw musisz wiedzieć, że zwolniłeś ostatnie odniesienie (w tym wszelkie odniesienia wewnętrzne, o których nie wiedziałeś), pozwalając GC na zwolnienie go. (W przeciwieństwie do innych implementacji, przynajmniej CPython zwalnia obiekt, gdy tylko jest to dozwolone). Zwykle zwalnia to co najmniej dwie rzeczy na następnym poziomie niższym (np. W przypadku łańcucha zwalniasz
PyString
obiekt, a bufor ciągu ).Jeśli robić zwalnianie obiektu, aby wiedzieć, czy to powoduje, że następny poziom niżej do deallocate blok przechowywania obiektów, trzeba znać stan wewnętrzny podzielnika obiektu, a także jak to jest realizowane. (Oczywiście nie może się to zdarzyć, chyba że zwolnisz ostatnią rzecz w bloku, a nawet wtedy może się to nie zdarzyć).
Jeśli zrobić deallocate blok przechowywania obiektów, aby wiedzieć, czy powoduje to
free
wezwanie, trzeba znać stan wewnętrzny podzielnika PyMem, a także jak to jest realizowane. (Ponownie, musisz cofnąć przydział ostatniego używanego bloku wmalloc
regionie ed, a nawet wtedy może się to nie zdarzyć).Jeśli zrobić
free
tomalloc
region, ED, aby wiedzieć, czy powoduje tomunmap
lub jego odpowiednik (lubbrk
), trzeba znać stan wewnętrznejmalloc
, a także jak to jest realizowane. A ten, w przeciwieństwie do innych, jest wysoce specyficzny dla platformy. (I znowu, generalnie musisz cofnąć przydział ostatniego używanegomalloc
wmmap
segmencie, a nawet wtedy może się to nie zdarzyć).Więc jeśli chcesz zrozumieć, dlaczego wydarzyło się dokładnie 50,5 MB, będziesz musiał prześledzić to od dołu do góry. Dlaczego
malloc
odmapowano strony o wartości 50,5 MB, gdy wykonałeś te jedno lub więcejfree
połączeń (prawdopodobnie dla nieco ponad 50,5 MB)? Musisz przeczytać platformęmalloc
, a następnie przejść przez różne tabele i listy, aby zobaczyć jej aktualny stan. (Na niektórych platformach może nawet wykorzystywać informacje na poziomie systemu, których prawie niemożliwe jest przechwycenie bez wykonania migawki systemu w celu sprawdzenia w trybie offline, ale na szczęście zwykle nie stanowi to problemu). A potem musisz zrób to samo na 3 poziomach powyżej.Tak więc jedyną przydatną odpowiedzią na to pytanie jest „Ponieważ”.
Jeśli nie wykonujesz programowania z ograniczeniem zasobów (np. Osadzonym), nie masz powodu, aby przejmować się tymi szczegółami.
A jeśli zajmujesz się rozwojem z ograniczonymi zasobami, znajomość tych szczegółów jest bezużyteczna; w zasadzie trzeba wykonać końcowe omówienie wszystkich tych poziomów, a konkretnie
mmap
potrzebnej pamięci na poziomie aplikacji (być może z jednym prostym, dobrze zrozumiałym alokatorem stref specyficznym dla aplikacji pomiędzy).źródło
Najpierw możesz zainstalować spojrzenia:
Następnie uruchom go w terminalu!
W kodzie Pythona dodaj na początku pliku:
Po użyciu zmiennej „Big” (na przykład: myBigVar), dla której chciałbyś zwolnić pamięć, napisz w swoim kodzie Pythona:
Na innym terminalu uruchom swój kod w Pythonie i obserwuj w terminalu „spojrzenia”, jak zarządzana jest pamięć w Twoim systemie!
Powodzenia!
PS Zakładam, że pracujesz na systemie Debian lub Ubuntu
źródło