eryksun odpowiedział na pytanie nr 1, a ja odpowiedziałem na pytanie nr 3 (oryginalne nr 4), ale teraz odpowiedzmy na pytanie nr 2:
Dlaczego w szczególności uwalnia 50,5 MB - na jakiej podstawie jest uwalniana kwota?
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ż go MAP_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łujesz free
, 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 w malloc
regionie ed, a nawet wtedy może się to nie zdarzyć).
Jeśli zrobić free
to malloc
region, ED, aby wiedzieć, czy powoduje to munmap
lub jego odpowiednik (lub brk
), trzeba znać stan wewnętrznej malloc
, 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żywanego malloc
w mmap
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ęcej free
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).