Ein Rätsel, das bald behoben werden muss:
import pandas as pd
import numpy as np
arr = np.random.randn(100000, 5)
def leak():
for i in xrange(10000):
df = pd.DataFrame(arr.copy())
result = df.xs(1000)
# result = df.ix[5000]
if __name__ == '__main__':
leak()
Ok, das ist, mit einem Wort, verdammt. Wenn ich der for-Schleife gc.collect hinzufüge, geht kein Speicher mehr verloren:
import pandas as pd
import numpy as np
import gc
arr = np.random.randn(100000, 5)
def leak():
pd.util.testing.set_trace()
for i in xrange(10000):
df = pd.DataFrame(arr.copy())
result = df.xs(1000)
gc.collect()
# result = df.ix[5000]
if __name__ == '__main__':
leak()
Es gibt hier Objekte, die nur dann Müll sammeln, wenn der zyklische GC ausgeführt wird. Was ist die Lösung hier? Unterbrechen Sie den Zyklus explizit in __del__
damit der Python-Speicherzuweiser aufhört, uns zu verarschen?
Können Sie das versuchen:
from ctypes import cdll, CDLL
import pandas as pd
import numpy as np
arr = np.random.randn(100000, 5)
cdll.LoadLibrary("libc.so.6")
libc = CDLL("libc.so.6")
def leak():
for i in xrange(10000):
libc.malloc_trim(0)
df = pd.DataFrame(arr.copy())
result = df.xs(1000)
# result = df.ix[5000]
if __name__ == '__main__':
leak()
Ich vermute, das hat nichts mit Python zu tun, aber das würde es bestätigen.
Ja, das schien den Trick zu tun. Speicherauslastung 450 MB nach dem Ausführen in IPython, dann hat malloc_trim 400 MB freigegeben. Sehr schädlich
Nach dem Upstream-Vorsprung von malloc_trim
sieht dies nach einer fehlgeschlagenen Glibc-Optimierung aus.
xref:
http://sourceware.org/bugzilla/show_bug.cgi?id=14827
Siehe Kommentar "Fastbins".
In [1]: from ctypes import Structure,c_int,cdll,CDLL
...: class MallInfo(Structure):
...: _fields_ =[
...: ( 'arena',c_int ), # /* Non-mmapped space allocated (bytes) */
...: ('ordblks',c_int ),# /* Number of free chunks */
...: ( 'smblks',c_int ), # /* Number of free fastbin blocks */
...: ( 'hblks',c_int ), #/* Number of mmapped regions */
...: ( 'hblkhd' ,c_int ), #/* Space allocated in mmapped regions (bytes) */
...: ( 'usmblks' ,c_int), # /* Maximum total allocated space (bytes) */
...: ( 'fsmblks' ,c_int) ,#/* Space in freed fastbin blocks (bytes) */
...: ( 'uordblks' ,c_int),# /* Total allocated space (bytes) */
...: ( 'fordblks',c_int ),# /* Total free space (bytes) */
...: ( 'keepcost',c_int )# /* Top-most, releasable space (bytes) */
...: ]
...: def __repr__(self):
...: return "\n".join(["%s:%d" % (k,getattr(self,k)) for k,v in self._fields_])
...:
...: cdll.LoadLibrary("libc.so.6")
...: libc = CDLL("libc.so.6")
...: mallinfo=libc.mallinfo
...: mallinfo.restype=MallInfo
...: libc.malloc_trim(0)
...: mallinfo().fsmblks
Out[1]: 0
In [2]: import numpy as np
...: import pandas as pd
...: arr = np.random.randn(100000, 5)
...: def leak():
...: for i in xrange(10000):
...: df = pd.DataFrame(arr.copy())
...: result = df.xs(1000)
...: leak()
...: mallinfo().fsmblks
Out[2]: 128
In [3]: libc.malloc_trim(0)
...: mallinfo().fsmblks
Out[3]: 0
Wird dann nicht behoben. Vielleicht sollten wir Pandas eines Tages einige Hilfsfunktionen hinzufügen, um das Malloc-Trimmen durchzuführen
Eintrag in FAQ vielleicht?
Für die Aufzeichnung verwenden wir (+ @ sbneto) dies für eine Weile in der Vorbildung und machen es sehr gut:
# monkeypatches.py
# Solving memory leak problem in pandas
# https://github.com/pandas-dev/pandas/issues/2659#issuecomment-12021083
import pandas as pd
from ctypes import cdll, CDLL
try:
cdll.LoadLibrary("libc.so.6")
libc = CDLL("libc.so.6")
libc.malloc_trim(0)
except (OSError, AttributeError):
libc = None
__old_del = getattr(pd.DataFrame, '__del__', None)
def __new_del(self):
if __old_del:
__old_del(self)
libc.malloc_trim(0)
if libc:
print('Applying monkeypatch for pd.DataFrame.__del__', file=sys.stderr)
pd.DataFrame.__del__ = __new_del
else:
print('Skipping monkeypatch for pd.DataFrame.__del__: libc or malloc_trim() not found', file=sys.stderr)
@ Alanjds vielen Dank!
Es gibt aber noch andere betroffene Operationen :-(
Es ist SEHR seltsam, dass das obige Problem (Ausgabe von glibc) keine Reaktionen hat. Es betrifft ALLE Umgebungen von Linux-PCs und -Servern. Und nichts!!!
Ich weiß, du wirst mir sagen: ok, schreibe einen Patch! Ich werde es tun (UPD: aber es wird seltsam sein, weil ich nichts über Glibc-Code weiß). Aber selbst niemand weiß es.
Alle sagen: KDE leckt. Wer weiß - warum?! Niemand!
Open Source? Zum Schämen! Entschuldigung, aber es ist wahr für diese Situation.
Ich glaube an dich. 2 Jahre und keine Bewegung auf dieser Seite: /
Ich sage, auf dieser Seite zu reparieren und einen großen Schuldkommentar abzugeben, weil das Gabeln dort nicht durchführbar erscheint.
@alanjds Ihr Code hat ein Problem für mich behoben, das große Kopfschmerzen verursachte. Würdest du gerne erklären, wie sich Pandas standardmäßig verhalten und wie dein Code es behebt?
Sie können dieses Problem auch umgehen, indem Sie zu jemalloc
als Standardzuweiser wechseln. Führen Sie anstelle von python script.py
LD_PRELOAD=/usr/lib/libjemalloc.so python script.py
. Beachten Sie, dass der Pfad zu libjemalloc.so
auf Ihrem System unterschiedlich sein kann und dass Sie ihn zuerst mit Ihrem Paketmanager installieren müssen.
@tchristensenowlet Das Problem scheint im malloc
Code von glibc
. Anscheinend berücksichtigt die Implementierung von free
dort kein Flag, das nach einem bestimmten Schwellenwert malloc_trim
ausgeben sollte, wie Sie im Link von @ghost sehen können. Daher wird malloc_trim
niemals aufgerufen und Speicherlecks. Wir haben nur malloc_trim
manuell aufgerufen, wenn die Bibliothek im System verfügbar ist. Wir nennen es in der __del__()
-Methode, die ausgeführt wird, wenn das Objekt durch Müll gesammelt wird.
glibc.malloc.mxfast
tunable wurde in Glibc eingeführt (https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html).
Ich denke, dies könnte der Schuldige in einem unserer Projekte sein, aber unsere Benutzer verwenden Windows mit Standard-Python 3.8 (von der offiziellen Website) und mit allen über pip installierten Abhängigkeiten. Wäre dieses Problem auch unter Windows? Wenn ja, was wäre das Äquivalent von cdll.LoadLibrary("libc.so.6")
?
Bearbeiten: Ich habe diesen hier beschriebenen Test ausgeführt und der gesammelte Müll hat jedes Mal seine Arbeit richtig gemacht:
https://github.com/pandas-dev/pandas/issues/21353
System: Windows 10
Python: 3.8.5
Pandas: 1.1.0
Hilfreichster Kommentar
Für die Aufzeichnung verwenden wir (+ @ sbneto) dies für eine Weile in der Vorbildung und machen es sehr gut: