Pandas: Проблемы с циклическим сборщиком мусора

Созданный на 8 янв. 2013  ·  14Комментарии  ·  Источник: pandas-dev/pandas

Загадка, которую предстоит вскоре раскрыть:

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

Самый полезный комментарий

Для записи, мы (+ @ 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)

Все 14 Комментарий

Ладно, словом, облажался. Если я добавлю 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. Кто знает - почему ?! Никто!

Открытый источник? Позор! Извините, но это правда для данной ситуации.

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

Я верю в тебя. 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

Была ли эта страница полезной?
1 / 5 - 1 рейтинги