Python-praca nad wyciekami pamięci

Mam program Pythona, który przeprowadza serię eksperymentów, bez danych przeznaczonych do przechowywania z jednego testu do drugiego. Mój kod zawiera wyciek pamięci, którego nie jestem w stanie znaleźć (sprawdziłem inne wątki {[2] } dotyczące wycieków pamięci). Ze względu na ograniczenia czasowe, musiałem zrezygnować z szukania przecieku, ale gdybym był w stanie wyizolować każdy eksperyment, program prawdopodobnie działałby wystarczająco długo, aby uzyskać wyniki, których potrzebuję.

  • przeprowadzi każdy test w osobny wątek pomoc?
  • czy są jakieś inne metody izolowania skutków przecieku?

Szczegóły dotyczące konkretnej sytuacji

  • Mój kod składa się z dwóch części: biegacza eksperymentu i rzeczywistego kodu eksperymentu.
  • chociaż żadne globale nie są współdzielone między kodem do uruchamiania wszystkich eksperymentów i kodem używanym przez każdy eksperyment, niektóre klasy/funkcje są koniecznie współdzielone.
  • experiment runner to nie tylko prosta pętla, która może być łatwo umieścić w skrypcie powłoki. Najpierw decyduje o testach, które należy uruchomić, biorąc pod uwagę parametry konfiguracyjne, następnie uruchamia testy, a następnie wysyła dane w określony sposób.
  • próbowałem ręcznie wywołać garbage collector na wypadek, gdyby problem polegał na tym, że garbage collector nie był uruchamiany, ale to nie działało

Update

Odpowiedź Gnibblera pozwoliła mi dowiedzieć się, że moje Obiekty ClosenessCalculation, które przechowują wszystkie z danych użytych podczas każdego obliczenia nie są usuwane. Następnie użyłem tego do ręcznego usunięcia niektórych linków, które wydają się naprawiać problemy z pamięcią.

Author: Community, 2009-10-29

4 answers

Możesz użyć czegoś takiego, aby pomóc wyśledzić wycieki pamięci

>>> from collections import defaultdict
>>> from gc import get_objects
>>> before = defaultdict(int)
>>> after = defaultdict(int)
>>> for i in get_objects():
...     before[type(i)] += 1 
... 

Teraz Załóżmy, że testy wyciekają trochę pamięci

>>> leaked_things = [[x] for x in range(10)]
>>> for i in get_objects():
...     after[type(i)] += 1
... 
>>> print [(k, after[k] - before[k]) for k in after if after[k] - before[k]]
[(<type 'list'>, 11)]

11 ponieważ wyciekła jedna lista zawierająca 10 kolejnych list

 53
Author: John La Rooy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-19 12:48:29

Wątki nie pomogą. Jeśli musisz zrezygnować ze znajdowania wycieku, jedynym rozwiązaniem, które powstrzyma jego efekt, jest uruchamianie od czasu do czasu nowego procesu (np. gdy test pozostawił całkowite zużycie pamięci zbyt wysokie dla Twoich upodobań-możesz łatwo określić rozmiar maszyny wirtualnej, czytając /proc/self/status w Linuksie i inne podobne podejścia w innych systemach operacyjnych).

Upewnij się, że cały skrypt pobiera opcjonalny parametr, aby powiedzieć mu, od jakiego numeru testu (lub innej identyfikacji testu) zacząć, tak, że gdy jedna instancja skryptu zdecyduje, że zajmuje zbyt dużo pamięci, może powiedzieć swojemu następcy, skąd ma się ponownie uruchomić.

Lub, bardziej solidnie, upewnij się, że po zakończeniu każdego testu jego identyfikacja jest dołączana do jakiegoś pliku o znanej nazwie. Po uruchomieniu programu zaczyna się od odczytu tego pliku i dzięki temu wie, jakie testy zostały już uruchomione. Ta architektura jest bardziej solidna, ponieważ obejmuje również przypadek, w którym program ulega awarii podczas testu; oczywiście, aby w pełni zautomatyzować odzyskiwanie po takich awariach, będziesz chciał, aby oddzielny program watchdog i proces byli odpowiedzialni za uruchamianie nowej instancji programu testowego, gdy stwierdzi, że poprzednia zawiesiła się (może użyć subprocess do tego celu -- potrzebuje również sposobu, aby powiedzieć, kiedy sekwencja jest zakończona, np. normalne wyjście z programu testowego może oznaczać, że podczas każdej awarii lub zakończenia ze statusem != 0 oznacza potrzebę uruchomienia nowej instancji fresh).

Jeśli te architektury przemawiają ale potrzebujesz dalszej pomocy w ich implementacji, po prostu skomentuj tę odpowiedź, a ja chętnie podam przykładowy kod - nie chcę tego robić "prewencyjnie" w przypadku, gdy są jeszcze niewyjaśnione problemy, które sprawiają, że architektury nie nadają się dla Ciebie. (Może również pomóc wiedzieć, na jakich platformach musisz działać).

 4
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-29 02:11:17

Chciałbym po prostu refactor eksperymentów do poszczególnych funkcji (jeśli nie tak już) następnie zaakceptować numer eksperymentu z linii poleceń, która wywołuje funkcję pojedynczego eksperymentu.

Wystarczy dodać skrypt powłoki w następujący sposób:

#!/bin/bash

for expnum in 1 2 3 4 5 6 7 8 9 10 11 ; do
    python youProgram ${expnum} otherParams
done

W ten sposób możesz pozostawić większość kodu tak, jak jest, a to usunie wszelkie wycieki pamięci, które myślisz, że masz pomiędzy każdym eksperymentem.

Oczywiście najlepszym rozwiązaniem jest zawsze znalezienie i naprawienie przyczyny problem, ale, jak już stwierdziłeś, nie jest to opcja dla Ciebie.

Chociaż trudno sobie wyobrazić wyciek pamięci w Pythonie, wierzę ci na słowo - możesz chociaż rozważyć możliwość, że się mylisz. Rozważ podniesienie tego w osobnym pytaniu, czegoś, nad czym możemy pracować z niskim priorytetem(w przeciwieństwie do tej wersji quick-fix).

Update: Tworzenie wiki społeczności, ponieważ pytanie zmieniło się nieco od oryginał. Chciałbym usunąć odpowiedź, ale dla faktu, że nadal uważam, że jest to przydatne - można zrobić to samo do biegacza eksperymentu, jak zaproponowałem skrypt bash, po prostu trzeba upewnić się, że eksperymenty są oddzielne procesy tak, że wycieki pamięci nie występują (jeśli wycieki pamięci są w biegaczu, będziesz musiał zrobić analizę przyczyn źródłowych i naprawić błąd poprawnie).

 2
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-29 02:48:24

Miałem ten sam problem z trzecią biblioteką C, która przeciekała. Najbardziej czystą pracą, o której mogłem myśleć, było widelec i czekać. Zaletą tego jest to, że nie musisz nawet tworzyć oddzielnego procesu po każdym uruchomieniu. Możesz określić rozmiar swojej partii.

Oto ogólne rozwiązanie (jeśli kiedykolwiek znajdziesz wyciek, jedyną zmianą, którą musisz wprowadzić, jest zmiana run (), aby wywołać run_single_process () zamiast run_forked () i gotowe):

import os,sys
batchSize = 20

class Runner(object):
    def __init__(self,dataFeedGenerator,dataProcessor):
        self._dataFeed = dataFeedGenerator
        self._caller = dataProcessor

    def run(self):
        self.run_forked()

    def run_forked(self):
        dataFeed = self._dataFeed
        dataSubFeed = []
        for i,dataMorsel in enumerate(dataFeed,1):
            if i % batchSize > 0:
                dataSubFeed.append(dataMorsel)
            else:
                self._dataFeed = dataSubFeed
                self.fork()
                dataSubFeed = []
                if self._child_pid is 0:
                    self.run_single_process()
                self.endBatch()

    def run_single_process(self)
        for dataMorsel in self._dataFeed:
            self._caller(dataMorsel)

    def fork(self):
        self._child_pid = os.fork()

    def endBatch(self):
        if self._child_pid is not 0:
            os.waitpid(self._child_pid, 0)
        else:
            sys.exit() # exit from the child when done

To izoluje wyciek pamięci do procesu potomnego. I nigdy nie wycieknie więcej razy niż wartość zmiennej batchSize.

 2
Author: Dmitry Rubanovich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-19 21:14:24