Загадка, которую предстоит вскоре раскрыть:
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()
Ладно, словом, облажался. Если я добавлю gc.collect к этому циклу for, он прекратит утечку памяти:
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()
Здесь есть объекты, которые собирают мусор только при выполнении циклического сборщика мусора. Какое здесь решение, явно прервать цикл в __del__
чтобы распределитель памяти Python перестал нас клевать?
Вы можете попробовать это:
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()
Я подозреваю, что это не имеет ничего общего с python, но это подтвердит это.
Да, похоже, это сработало. Использование памяти 450 МБ после запуска в IPython, затем malloc_trim освободил 400 МБ. Очень пагубно
Следуя за лидером malloc_trim
вверх, это выглядит как неудачная оптимизация glibc.
xref:
http://sourceware.org/bugzilla/show_bug.cgi?id=14827
см. комментарий "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
Тогда не исправлю. Возможно, нам стоит когда-нибудь добавить в pandas вспомогательные функции, чтобы выполнить обрезку malloc
Может, запись в FAQ?
Для записи, мы (+ @ sbneto) некоторое время используем это в преддукции, и у нас все очень хорошо:
# 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 большое спасибо!
Но есть и другие затронутые операции :-(
ОЧЕНЬ странно, что проблема выше (проблема glibc) не вызывает никаких реакций. Это влияет на ВСЕ среду ПК и серверов Linux. И ничего!!!
Я знаю, ты мне скажешь: ладно, напиши патч! Я сделаю это (UPD: но это будет странно, потому что я ничего не знаю о коде glibc). Но даже этого никто не знает.
Все говорят: утечки KDE. Кто знает - почему ?! Никто!
Открытый источник? Позор! Извините, но это правда для данной ситуации.
Я верю в тебя. 2 года и никаких переездов на ту сторону: /
Я предлагаю исправить с этой стороны и добавляю огромное обвинение, потому что там разветвление кажется невозможным.
@alanjds Ваш код сильную головную боль. Не могли бы вы объяснить, что такое поведение панд по умолчанию и как ваш код это исправляет?
Вы также можете обойти эту проблему, переключившись на jemalloc
качестве распределителя по умолчанию. Вместо python script.py
запустите LD_PRELOAD=/usr/lib/libjemalloc.so python script.py
. Обратите внимание, что путь к libjemalloc.so
может быть другим в вашей системе и что вам сначала нужно установить его с помощью диспетчера пакетов.
@tchristensenowlet Проблема, похоже, в malloc
коде glibc
. Очевидно, реализация free
там не учитывает флаг, который должен выдавать malloc_trim
после определенного порога, как вы можете видеть в ссылке @ghost . Следовательно, malloc_trim
никогда не вызывается и происходит утечка памяти. Мы просто вручную вызывали malloc_trim
если библиотека доступна в системе. Мы вызываем его в методе __del__()
, который выполняется при сборке мусора.
glibc.malloc.mxfast
tunable добавлено в Glibc (https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html).
Я думаю, что это может быть виновником одного из наших проектов, но наши пользователи работают с Windows с Python 3.8 по умолчанию (с официального сайта) и со всеми зависимостями, установленными через pip. Будет ли эта проблема возникать и в Windows? Если да, то каким будет эквивалент cdll.LoadLibrary("libc.so.6")
?
Изменить: я запускал этот тест, описанный здесь, и каждый раз собранный мусор выполнял свою работу правильно:
https://github.com/pandas-dev/pandas/issues/21353
Система: Windows 10
Python: 3.8.5
Панды: 1.1.0
Самый полезный комментарий
Для записи, мы (+ @ sbneto) некоторое время используем это в преддукции, и у нас все очень хорошо: