Pandas: Zyklische GC-Probleme

Erstellt am 8. Jan. 2013  ·  14Kommentare  ·  Quelle: pandas-dev/pandas

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()
Bug

Hilfreichster Kommentar

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)

Alle 14 Kommentare

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.

PS http://sourceware.org/bugzilla/show_bug.cgi?id=14827

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

War diese Seite hilfreich?
1 / 5 - 1 Bewertungen