Un mystère à déboguer bientôt:
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, c'est, en un mot, f * cked up. Si j'ajoute gc.collect à cette boucle for, il arrête de fuir de mémoire:
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()
Il y a des objets ici qui ne sont récupérés que lorsque le GC cyclique s'exécute. Quelle est la solution ici, interrompre explicitement le cycle dans __del__
pour que l'allocateur de mémoire Python cesse de nous foutre en l'air?
Pouvez-vous essayer ceci:
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()
Je soupçonne que cela n'a rien à voir avec python, mais cela le confirmerait.
Ouais, ça a semblé faire l'affaire. Utilisation de la mémoire 450 Mo après avoir exécuté cela dans IPython, puis malloc_trim a libéré 400 Mo. Très pernicieux
Suite à l'avance malloc_trim
amont, cela ressemble à une optimisation de la glibc qui a mal tourné.
xréf:
http://sourceware.org/bugzilla/show_bug.cgi?id=14827
voir le commentaire "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
Je ne vais pas réparer alors. Peut-être que nous devrions ajouter un jour des fonctions d'assistance aux pandas pour faire la coupe de malloc
Entrée dans la FAQ, peut-être?
Pour mémoire, nous (+ @ sbneto) l'utilisons en préduction depuis un peu de temps, et nous nous en tirons très bien:
# 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 merci beaucoup!
Mais il y a d'autres opérations affectées :-(
C'est TRÈS étrange que le problème ci-dessus (problème de glibc) n'ait aucune réaction. Il affecte TOUS l'environnement des PC et serveurs Linux. Et rien!!!
Je sais, tu me diras: ok, écris un patch! Je vais le faire (UPD: mais ce sera étrange car je ne sais rien sur le code glibc). Mais même personne ne le sait.
Tout le monde dit: KDE fuit. Qui sait - pourquoi?! Personne!
Open source? Pour la honte! Désolé mais c'est vrai pour cette situation.
Je crois en toi. 2 ans et pas de mouvement de ce côté-là: /
Je dis de fixer de ce côté et de mettre un énorme commentaire de blâme, car bifurquer là-bas semble irréalisable.
@alanjds Votre code m'a corrigé un problème qui me causait de gros maux de tête. Seriez-vous prêt à expliquer quel est le comportement par défaut des pandas et comment votre code le corrige?
Vous pouvez également contourner ce problème en passant à jemalloc
comme allocateur par défaut. Au lieu de python script.py
, exécutez LD_PRELOAD=/usr/lib/libjemalloc.so python script.py
. Notez que le chemin vers libjemalloc.so
peut être différent sur votre système et que vous devez d'abord l'installer avec votre gestionnaire de paquets.
@tchristensenowlet Le problème semble être dans le code malloc
de glibc
. Apparemment, l'implémentation de free
là-bas ne respecte pas un drapeau qui devrait émettre malloc_trim
après un certain seuil, comme vous pouvez le voir dans le lien de @ghost . Par conséquent, malloc_trim
n'est jamais appelé et la mémoire fuit. Nous avons simplement appelé manuellement malloc_trim
si la bibliothèque est disponible dans le système. Nous l'appelons dans la méthode __del__()
, qui est exécutée lorsque l'objet est garbage collection.
glibc.malloc.mxfast
tunable a été introduit dans Glibc (https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html).
Je pense que cela pourrait être le coupable dans l'un de nos projets, mais nos utilisateurs exécutent Windows avec Python 3.8 par défaut (à partir du site officiel) et avec toutes les dépendances installées via pip. Ce problème serait-il également sous Windows? Si oui, quel serait l'équivalent cdll.LoadLibrary("libc.so.6")
?
Edit: J'ai exécuté ces tests décrits ici, et le ramasse-miettes a fait son travail correctement à chaque fois:
https://github.com/pandas-dev/pandas/issues/21353
Système: Windows 10
Python: 3.8.5
Pandas: 1.1.0
Commentaire le plus utile
Pour mémoire, nous (+ @ sbneto) l'utilisons en préduction depuis un peu de temps, et nous nous en tirons très bien: