
W Zendesk używamy Pythona do budowania produktów uczenia maszynowego (ML). Jednym z powszechnych problemów wydajnościowych, które napotkaliśmy w aplikacjach uczenia maszynowego są wycieki pamięci i spikes. Kod Pythona jest zazwyczaj wykonywany w kontenerach za pomocą frameworków przetwarzania rozproszonego, takich jak Hadoop, Spark i AWS Batch. Każdemu kontenerowi przydzielana jest stała ilość pamięci. Gdy wykonanie kodu przekroczy określony limit pamięci, kontener zostanie zamknięty z powodu błędów braku pamięci.
Szybką poprawką jest zwiększenie przydziału pamięci. Jednak może to spowodować marnotrawstwo zasobów i wpłynąć na stabilność produktów z powodu nieprzewidywalnych skoków pamięci. Przyczyny wycieków pamięci mogą obejmować:
- lingowanie dużych obiektów, które nie są zwalniane
- cykle referencyjne w kodzie
- podstawowe biblioteki/rozszerzenia przeciekające pamięć
Przydatnym ćwiczeniem jest profilowanie użycia pamięci przez aplikacje, aby lepiej zrozumieć wydajność przestrzenną kodu i używanych pakietów bazowych. Ten post obejmuje:
- profilowanie użycia pamięci w aplikacji w czasie
- jak sprawdzić użycie pamięci w określonej części programu
- wskazówki dotyczące debugowania problemów z pamięcią
Możesz spojrzeć na użycie pamięci zmieniające się w czasie podczas wykonywania kodu Pythona używając pakietu memory-profile.
# install the required packages
pip install memory_profiler
pip install matplotlib# run the profiler to record the memory usage
# sample 0.1s by defaut
mprof run --include-children python fantastic_model_building_code.py# plot the recorded memory usage
mprof plot --output memory-profile.png

Opcja include-children powoduje uwzględnienie wykorzystania pamięci przez procesy potomne wywołane przez proces macierzysty. Wykres A przedstawia iteracyjny proces trenowania modelu, który powoduje cykliczny przyrost pamięci w miarę przetwarzania kolejnych partii danych treningowych. Obiekty są uwalniane po rozpoczęciu zbierania śmieci.
Jeśli użycie pamięci stale rośnie, istnieje potencjalny problem wycieków pamięci. Oto przykładowy skrypt, który to zobrazuje.

Punkt przerwania debugera może być ustawiony, gdy użycie pamięci przekroczy pewien próg przy użyciu opcji pdb-mmem, co jest przydatne przy rozwiązywaniu problemów.
Zrzut pamięci w pewnym momencie
Ważne jest zrozumienie oczekiwanej liczby dużych obiektów w programie i tego, czy powinny one być duplikowane i/lub przekształcane do różnych formatów.
Aby dalej analizować obiekty w pamięci, można utworzyć zrzut sterty podczas pewnych linii kodu w programie za pomocą muppy.

Inną użyteczną biblioteką do profilowania pamięci jest objgraph, która może generować wykresy obiektów w celu zbadania linii obiektów.
Dążenie do szybkiej pętli sprzężenia zwrotnego
Przydatnym podejściem jest stworzenie małego „przypadku testowego”, który uruchamia tylko kod wycieku pamięci, o którym mowa. Rozważ użycie podzbioru losowo próbkowanych danych, jeśli pełne dane wejściowe są długie do uruchomienia.
Run memory intensive tasks in separate process
Python niekoniecznie zwalnia pamięć natychmiast z powrotem do systemu operacyjnego. Aby mieć pewność, że pamięć jest zwalniana po wykonaniu fragmentu kodu, musi on być uruchamiany w osobnym procesie. Ta strona zawiera więcej szczegółów na temat zbierania śmieci w Pythonie.
Debugger może dodawać referencje do obiektów
Jeśli używany jest debugger z punktem przerwania, taki jak pdb, wszelkie obiekty utworzone i przywołane ręcznie z debuggera pozostaną w profilu pamięci. Może to stworzyć fałszywe poczucie wycieków pamięci, gdzie obiekty nie są zwalniane w odpowiednim czasie.
Uwaga na pakiety, które mogą być nieszczelne
Niektóre biblioteki Pythona mogą potencjalnie mieć wycieki pamięci. Np. pandas ma całkiem sporo znanych problemów z wyciekami pamięci.
Szczęśliwe polowanie!
.