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, mallocktó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_FREEitp.
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ś brklub równoważnego do zmniejszenia segmentu danych (obecnie bardzo rzadko) lub użyłeś munmaplub 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_FIXEDza pomocą MADV_FREEsegmentu, którego natychmiast usuwasz).
Jednak większość programów nie alokuje bezpośrednio rzeczy ze stron pamięci; używają mallocalokatora w stylu. Kiedy wywołujesz free, alokator może zwolnić strony do systemu operacyjnego tylko wtedy, gdy akurat jesteś freeostatnim 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 PyStringobiekt, 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 freewezwanie, trzeba znać stan wewnętrzny podzielnika PyMem, a także jak to jest realizowane. (Ponownie, musisz cofnąć przydział ostatniego używanego bloku w mallocregionie ed, a nawet wtedy może się to nie zdarzyć).
Jeśli zrobić free to mallocregion, ED, aby wiedzieć, czy powoduje to munmaplub 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 mallocw mmapsegmencie, 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 mallocodmapowano strony o wartości 50,5 MB, gdy wykonałeś te jedno lub więcej freepołą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 mmappotrzebnej pamięci na poziomie aplikacji (być może z jednym prostym, dobrze zrozumiałym alokatorem stref specyficznym dla aplikacji pomiędzy).