Jak mogę jawnie zwolnić pamięć w Pythonie?


387

Napisałem program w języku Python, który działa na dużym pliku wejściowym, tworząc kilka milionów obiektów reprezentujących trójkąty. Algorytm to:

  1. odczytać plik wejściowy
  2. przetworzyć plik i utworzyć listę trójkątów reprezentowanych przez ich wierzchołki
  3. wypisz wierzchołki w formacie OFF: lista wierzchołków, a następnie lista trójkątów. Trójkąty są reprezentowane przez indeksy na liście wierzchołków

Wymóg WYŁ., Aby wydrukować pełną listę wierzchołków przed wydrukowaniem trójkątów, oznacza, że ​​muszę zapisać listę trójkątów w pamięci przed zapisaniem danych wyjściowych do pliku. W międzyczasie dostaję błędy pamięci z powodu rozmiarów list.

Jak najlepiej powiedzieć Pythonowi, że nie potrzebuję już niektórych danych i że można je uwolnić?


11
Dlaczego nie wydrukować trójkątów do pliku pośredniego i wczytać je ponownie, gdy będą potrzebne?
Alice Purcell,

2
To pytanie może potencjalnie dotyczyć dwóch zupełnie różnych rzeczy. Czy te błędy pochodzą z tego samego procesu Python , w którym to przypadku zależy nam na zwolnieniu pamięci na stercie procesu Python, czy też są to różne procesy w systemie, w którym to przypadku zależy nam na zwolnieniu pamięci do systemu operacyjnego?
Charles Duffy,

Odpowiedzi:


453

Zgodnie z oficjalną dokumentacją Pythona możesz zmusić Garbage Collector do zwolnienia pamięci, do której się nie odwołuje gc.collect(). Przykład:

import gc
gc.collect()

19
Rzeczy i tak są często zbierane śmieci, z wyjątkiem nietypowych przypadków, więc nie sądzę, aby to bardzo pomogło.
Lennart Regebro

24
Zasadniczo należy unikać gc.collect (). Śmieciarka wie, jak wykonać swoją pracę. To powiedziawszy, jeśli OP znajduje się w sytuacji, gdy nagle zwalnia wiele obiektów (jak w milionach), gc.collect może się przydać.
Jason Baker,

164
Właściwie nazywanie gc.collect()się na końcu pętli może pomóc uniknąć fragmentacji pamięci, co z kolei pomaga zwiększyć wydajność. Widziałem, że robi to znaczącą różnicę (~ 20% czasu działania IIRC)
RobM

38
Używam Pythona 3.6. Wywołanie gc.collect()po załadowaniu dataframe pandy z hdf5 (500k wierszy) Zmniejszone zużycie pamięci z 1.7GB do 500MB
John

15
Muszę załadować i przetworzyć kilka tablic numpy o pojemności 25 GB w systemie z pamięcią 32 GB. Używanie del my_arraypo którym następuje gc.collect()po przetworzeniu tablicy jest jedynym sposobem, w jaki pamięć jest faktycznie zwalniana, a mój proces przetrwa, aby załadować następną tablicę.
David

113

Niestety (w zależności od wersji i wydania Pythona) niektóre typy obiektów używają „wolnych list”, które są zgrabną lokalną optymalizacją, ale mogą powodować fragmentację pamięci, w szczególności przez zwiększanie ilości „pamięci” przeznaczonej tylko na obiekty określonego typu i przez to niedostępny dla „funduszu ogólnego”.

Jedynym naprawdę niezawodnym sposobem, aby zapewnić, że duże, ale tymczasowe użycie pamięci ZRODZIE wszystkie zasoby do systemu po zakończeniu, jest użycie tego w podprocesie, co powoduje, że praca wymagająca dużej ilości pamięci kończy się. W takich warunkach system operacyjny wykona swoje zadanie i chętnie zutylizuje wszystkie zasoby, które podproces mógł pochłonąć. Na szczęście multiprocessingmoduł sprawia, że ​​tego rodzaju operacje (które kiedyś były raczej uciążliwe) nie są takie złe w nowoczesnych wersjach Pythona.

W twoim przypadku wydaje się, że najlepszym sposobem, aby podprocesy zgromadziły niektóre wyniki, a jednocześnie upewnić się, że wyniki te są dostępne dla głównego procesu, jest użycie plików tymczasowych (przez okres przejściowy mam na myśli, NIE rodzaj plików, które automatycznie znikają po zamknięciu, zwykłe pliki, które jawnie usuwasz, gdy już z nimi skończysz).


31
Z pewnością chciałbym zobaczyć ten trywialny przykład.
Aaron Hall

3
Poważnie. Co powiedział @AaronHall.
Noob Saibot

17
@AaronHall Trywialny przykład teraz dostępny , wykorzystujący multiprocessing.Managerpliki zamiast implementować stan współdzielony.
user4815162342

48

delZestawienie może być przydatne, ale IIRC to nie jest gwarantowane, aby zwolnić pamięć . Dokumenty są tutaj ... i dlaczego nie został wydany jest tutaj .

Słyszałem, jak ludzie w systemach Linux i Unix rozpakowują proces Pythona, aby wykonać jakąś pracę, uzyskiwać wyniki, a następnie zabijać.

W tym artykule znajdują się uwagi na temat modułu śmieciowego Pythona, ale myślę, że brak kontroli pamięci jest wadą pamięci zarządzanej


Czy IronPython i Jython byłyby inną opcją, aby uniknąć tego problemu?
Esteban Küber

@voyager: Nie, nie byłoby. I tak naprawdę żaden inny język. Problem polega na tym, że wczytuje duże ilości danych do listy, a dane są zbyt duże dla pamięci.
Lennart Regebro

1
Prawdopodobnie byłoby gorzej pod IronPython lub Jython. W tych środowiskach nie masz nawet gwarancji, że pamięć zostanie zwolniona, jeśli nic innego nie zawiera referencji.
Jason Baker,

@voyager, tak, ponieważ wirtualna maszyna Java szuka globalnie wolnej pamięci. Dla JVM Jython nie jest niczym specjalnym. Z drugiej strony JVM ma swoją wadę, na przykład musisz wcześniej zadeklarować, jak dużą stertę może wykorzystać.
Umowa prof. Falkena została naruszona

32

Python jest zbierany w pamięci, więc jeśli zmniejszysz rozmiar listy, odzyska pamięć. Możesz także użyć instrukcji „del”, aby całkowicie pozbyć się zmiennej:

biglist = [blah,blah,blah]
#...
del biglist

18
To jest i nie jest prawdą. Zmniejszenie rozmiaru listy pozwala na odzyskanie pamięci, ale nie ma gwarancji, kiedy to nastąpi.
user142350,

3
Nie, ale zazwyczaj to pomoże. Jednak, jak rozumiem pytanie, problem polega na tym, że musi mieć tak wiele obiektów, że zabraknie mu pamięci przed przetworzeniem ich wszystkich, jeśli odczyta je na listę. Usunięcie listy przed zakończeniem przetwarzania raczej nie będzie użytecznym rozwiązaniem. ;)
Lennart Regebro

3
Czy stan niskiego poziomu pamięci / braku pamięci nie uruchomiłby „awaryjnego uruchomienia” modułu wyrzucającego śmieci?
Jeremy Friesner,

4
czy biglist = [] zwolni pamięć?
neouyghur

3
tak, jeśli do starej listy nie odwołuje się nic innego.
Ned Batchelder

22

Nie można jawnie zwolnić pamięci. Musisz upewnić się, że nie przechowujesz odniesień do obiektów. Będą następnie zbierane śmieci, uwalniając pamięć.

W twoim przypadku, gdy potrzebujesz dużych list, zwykle musisz zreorganizować kod, zwykle za pomocą generatorów / iteratorów. W ten sposób nie musisz w ogóle mieć dużych list w pamięci.

http://www.prasannatech.net/2009/07/introduction-python-generators.html


1
Jeśli takie podejście jest wykonalne, prawdopodobnie warto to zrobić. Należy jednak zauważyć, że nie można uzyskać losowego dostępu do iteratorów, co może powodować problemy.
Jason Baker

To prawda, a jeśli to konieczne, losowy dostęp do dużych zestawów danych może wymagać pewnego rodzaju bazy danych.
Lennart Regebro

Za pomocą iteratora można łatwo wyodrębnić losowy podzbiór innego iteratora.
S.Lott,

To prawda, ale musiałbyś iterować wszystko, aby uzyskać podzbiór, który będzie bardzo wolny.
Lennart Regebro

21

( delmoże być twoim przyjacielem, ponieważ zaznacza obiekty jako możliwe do usunięcia, gdy nie ma innych odniesień do nich. Teraz często interpreter CPython zachowuje tę pamięć do późniejszego wykorzystania, więc twój system operacyjny może nie widzieć pamięci „zwolnionej”).

Być może nie napotkasz żadnego problemu z pamięcią, używając bardziej zwartej struktury danych. Zatem listy liczb są znacznie mniej wydajne pod względem pamięci niż format używany przez arraymoduł standardowy lub numpymoduł innej firmy . Zaoszczędziłbyś pamięć, umieszczając swoje wierzchołki w tablicy NumPy 3xN, a trójkąty w tablicy N-elementowej.


Co? Śmieciowanie CPython opiera się na liczeniu; nie jest to okresowy test typu „przeciągnij i przeciągnij” (jak w przypadku wielu popularnych implementacji JVM), ale zamiast tego natychmiast usuwa coś, gdy liczba odniesień osiągnie zero. Tylko cykle (w których przeliczenia byłyby równe zero, ale nie były spowodowane pętlami w drzewie referencyjnym) wymagają okresowej konserwacji. delnie robi niczego, co po prostu nie przypisałoby innej wartości do wszystkich nazw odwołujących się do obiektu.
Charles Duffy,

Widzę, skąd pochodzisz: odpowiednio zaktualizuję odpowiedź. Rozumiem, że interpreter CPython faktycznie działa w jakiś pośredni sposób: deluwalnia pamięć z punktu widzenia Pythona, ale ogólnie nie z punktu widzenia biblioteki wykonawczej C lub systemu operacyjnego. Odnośniki: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/… .
Eric O Lebigot,

Zgadzamy się co do treści twoich linków, ale zakładając, że OP mówi o błędzie, jaki otrzymują z tego samego procesu Pythona , różnica między zwolnieniem pamięci na stercie lokalnej procesu a systemem operacyjnym wydaje się mało istotna ( zwolnienie do sterty powoduje, że przestrzeń ta jest dostępna na nowe przydziały w ramach tego procesu Pythona). I do tego deljest równie skuteczny w przypadku wyjść poza zakres, przeniesień itp.
Charles Duffy,

11

Miałem podobny problem z odczytem wykresu z pliku. Przetwarzanie obejmowało obliczenie macierzy zmiennoprzecinkowej 200 000 x 200 000 (jedna linia na raz), która nie mieściła się w pamięci. Próba zwolnienia pamięci między obliczeniami za pomocą gc.collect()naprawiła aspekt problemu związany z pamięcią, ale spowodowało to problemy z wydajnością: nie wiem dlaczego, ale chociaż ilość używanej pamięci pozostała stała, każde nowe wywołanie gc.collect()trwało dłużej niż Poprzedni. Tak szybko gromadzenie śmieci zajęło większość czasu obliczeń.

Aby rozwiązać zarówno problemy z pamięcią, jak i wydajnością, przełączyłem się na sztuczkę wielowątkową, którą gdzieś przeczytałem (przepraszam, nie mogę już znaleźć powiązanego postu). Zanim czytałem każdą linię pliku w dużej forpętli, przetwarzałem ją i gc.collect()co jakiś czas działałem w celu zwolnienia miejsca w pamięci. Teraz wywołuję funkcję, która odczytuje i przetwarza fragment pliku w nowym wątku. Po zakończeniu wątku pamięć jest automatycznie zwalniana bez dziwnego problemu z wydajnością.

Praktycznie działa tak:

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided

1
Zastanawiam się, dlaczego używasz `//` `zamiast Python w komentarzach.
JC Rocamonde,

Zmieszałem się między językami. Dziękuję za uwagę, zaktualizowałem składnię.
Retzod,

9

Inni podali pewne sposoby na „nakłonienie” interpretera Pythona do zwolnienia pamięci (lub w inny sposób uniknięcia problemów z pamięcią). Możliwe, że powinieneś najpierw wypróbować ich pomysły. Uważam jednak, że ważne jest udzielenie bezpośredniej odpowiedzi na twoje pytanie.

Tak naprawdę nie ma sposobu, aby bezpośrednio powiedzieć Pythonowi, aby zwolnił pamięć. Faktem jest, że jeśli chcesz mieć tak niski poziom kontroli, będziesz musiał napisać rozszerzenie w C lub C ++.

To powiedziawszy, istnieje kilka narzędzi, które mogą w tym pomóc:


3
gc.collect () i del gc.garbage [:] działać dobrze, gdy używam dużej ilości pamięci
Andrew Scott Evans

3

Jeśli nie obchodzi Cię ponowne użycie wierzchołków, możesz mieć dwa pliki wyjściowe - jeden dla wierzchołków i jeden dla trójkątów. Następnie po zakończeniu dołącz plik trójkąta do pliku wierzchołka.


1
Myślę, że mogę zachować tylko wierzchołki w pamięci i wydrukować trójkąty do pliku, a następnie wydrukować wierzchołki tylko na końcu. Jednak zapisywanie trójkątów do pliku jest ogromnym obciążeniem wydajności. Czy jest jakiś sposób, aby to przyspieszyć?
Nathan Fellman
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.