Próbuję znaleźć źródło nieprzyjemnego wycieku pamięci w programie Python/NumPy przy użyciu rozszerzeń C/Cython i multiprocessing
.Debugowanie wycieku pamięci Python/NumPy
Każdy podproces przetwarza listę obrazów, a dla każdego z nich wysyła macierz wyjściową (która zwykle ma wielkość około 200-300 MB) do głównego procesu przez Queue
. Dość standardowa mapa/zmniejsz konfigurację.
Jak można sobie wyobrazić, przeciek pamięci może przybrać gigantyczne proporcje z tak dużymi tablicami, a mając wiele procesów z radością przekroczyć 20 GB pamięci RAM, gdy potrzebują tylko 5-6 GB, jest ... denerwujący.
Próbowałem uruchomiony debugowania kompilacji Pythona poprzez Valgrind i poczwórną sprawdził moje rozszerzenia dla wycieków pamięci, ale nic nie znalazłem.
Sprawdziłem kod Pythona dla wiszących odwołań do moich tablic, a także użyłem NumPy's allocation tracker, aby sprawdzić, czy moje tablice zostały rzeczywiście zwolnione. Byli.
Ostatnią rzeczą, jaką zrobiłem było dołączenie GDB na jeden z moich procesów (ten zły chłopiec jest teraz działa przy 27GB RAM i liczenia) i dumping dużą część hałdy na dysku. Ku mojemu zaskoczeniu, porzucony plik był pełen zer! O wartości zerowej 7G.
Czy jest to standardowe zachowanie przydziału pamięci w Pythonie/NumPy? Czy tęskniłem za czymś oczywistym, co tłumaczyłoby posiadanie tak dużej ilości pamięci na nic? Jak prawidłowo zarządzać pamięcią?
EDIT: Dla przypomnienia, biegnę NumPy 1.7.1 i Python 2.7.3.
EDIT 2: Byłem monitorowanie procesu z strace
i wydaje się, że utrzymuje się na zwiększeniu punkt załamania każdego procesu (przy użyciu brk()
syscall).
Czy CPython rzeczywiście właściwie zwalnia pamięć? A co z rozszerzeniami C, tablicami NumPy? Kto decyduje, kiedy zadzwonić pod numer brk()
, czy jest to sam Python, czy jest to biblioteka bazowa (libc
, ...)?
Poniżej znajduje się przykładowy protokół strace z komentarzami, z jednej iteracji (tj. Jeden zestaw obrazów wejściowych). Zauważ, że punkt przerwania stale się zwiększa, ale upewniłem się (z objgraph
), że żadne znaczące tablice NumPy nie są przechowywane wewnątrz interpretera Pythona.
# Reading .inf files with metadata
# Pretty small, no brk()
open("1_tif_all/AIR00642_1.inf", O_RDONLY) = 6
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9387fff000
munmap(0x7f9387fff000, 4096) = 0
open("1_tif_all/AIR00642_2.inf", O_RDONLY) = 6
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9387fff000
munmap(0x7f9387fff000, 4096) = 0
open("1_tif_all/AIR00642_3.inf", O_RDONLY) = 6
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9387fff000
munmap(0x7f9387fff000, 4096) = 0
open("1_tif_all/AIR00642_4.inf", O_RDONLY) = 6
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9387fff000
munmap(0x7f9387fff000, 4096) = 0
# This is where I'm starting the heavy processing
write(2, "[INFO/MapProcess-1] Shot 642: Da"..., 68) = 68
write(2, "[INFO/MapProcess-1] Shot 642: Vi"..., 103) = 103
write(2, "[INFO/MapProcess-1] Shot 642: Re"..., 66) = 66
# I'm opening a .tif image (752 x 480, 8-bit, 1 channel)
open("1_tif_all/AIR00642_3.tif", O_RDONLY) = 6
read(6, "II*\0JC\4\0", 8) = 8
mmap(NULL, 279600, PROT_READ, MAP_SHARED, 6, 0) = 0x7f9387fbb000
munmap(0x7f9387fbb000, 279600) = 0
write(2, "[INFO/MapProcess-1] Shot 642: Pr"..., 53) = 53
# Another .tif
open("1_tif_all/AIR00642_4.tif", O_RDONLY) = 6
read(6, "II*\0\266\374\3\0", 8) = 8
mmap(NULL, 261532, PROT_READ, MAP_SHARED, 6, 0) = 0x7f9387fc0000
munmap(0x7f9387fc0000, 261532) = 0
write(2, "[INFO/MapProcess-1] Shot 642: Pr"..., 51) = 51
brk(0x1aea97000) = 0x1aea97000
# Another .tif
open("1_tif_all/AIR00642_1.tif", O_RDONLY) = 6
read(6, "II*\0\220\253\4\0", 8) = 8
mmap(NULL, 306294, PROT_READ, MAP_SHARED, 6, 0) = 0x7f9387fb5000
munmap(0x7f9387fb5000, 306294) = 0
brk(0x1af309000) = 0x1af309000
write(2, "[INFO/MapProcess-1] Shot 642: Pr"..., 53) = 53
brk(0x1b03da000) = 0x1b03da000
# Another .tif
open("1_tif_all/AIR00642_2.tif", O_RDONLY) = 6
mmap(NULL, 345726, PROT_READ, MAP_SHARED, 6, 0) = 0x7f9387fab000
munmap(0x7f9387fab000, 345726) = 0
brk(0x1b0c42000) = 0x1b0c42000
write(2, "[INFO/MapProcess-1] Shot 642: Pr"..., 51) = 51
# I'm done reading my images
write(2, "[INFO/MapProcess-1] Shot 642: Fi"..., 72) = 72
# Allocating some more arrays for additional variables
# Increases by about 8M at a time
brk(0x1b1453000) = 0x1b1453000
brk(0x1b1c63000) = 0x1b1c63000
brk(0x1b2473000) = 0x1b2473000
brk(0x1b2c84000) = 0x1b2c84000
brk(0x1b3494000) = 0x1b3494000
brk(0x1b3ca5000) = 0x1b3ca5000
# What are these mmap calls doing here?
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9377df1000
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9367be2000
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93579d3000
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93477c4000
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93375b5000
munmap(0x7f93579d3000, 270594048) = 0
munmap(0x7f93477c4000, 270594048) = 0
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93579d3000
munmap(0x7f93375b5000, 270594048) = 0
mmap(NULL, 50737152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9354970000
munmap(0x7f9354970000, 50737152) = 0
brk(0x1b4cc6000) = 0x1b4cc6000
brk(0x1b5ce7000) = 0x1b5ce7000
EDIT 3:Is freeing handled differently for small/large numpy arrays? mogą być istotne. Coraz bardziej jestem przekonany, że po prostu przydzielam zbyt wiele tablic, które nie zostaną udostępnione systemowi, ponieważ tak naprawdę jest to standardowe zachowanie. Postaram się wcześniej przydzielić moje tablice i ponownie je wykorzystać w razie potrzeby.
Czego używasz do odczytu plików obrazów? Miałem problemy z wyciekiem pamięci z obiektami PIL 'Image' w przeszłości –
Używam wiązań PyLibTiff. I rozwiązałem to, zobacz moją odpowiedź! –