Pandas: قضايا GC الدورية

تم إنشاؤها على ٨ يناير ٢٠١٣  ·  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()
Bug

التعليق الأكثر فائدة

للتسجيل ، نحن (+ @ 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 كومينتر

حسنًا ، هذا ، باختصار ، f * cked up. إذا أضفت 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()

توجد أشياء هنا يتم جمع القمامة فقط عند تشغيل GC الدوري. ما الحل هنا ، كسر الدورة صراحةً بـ __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 اتجاه المنبع ، يبدو أن هذا التحسين قد انحرف.
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

لن تصلح بعد ذلك. ربما يجب أن نضيف بعض الوظائف المساعدة إلى الباندا يومًا ما للقيام بعملية قص الملوك

الدخول في الأسئلة الشائعة ، ربما؟

للتسجيل ، نحن (+ @ 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. ولا شيء!!!

أعلم ، ستقول لي: حسنًا ، اكتب رقعة! سأفعل ذلك (محدث: لكنه سيكون غريبًا لأنني لا أعرف شيئًا عن كود glibc). لكن حتى لا أحد يعرف ذلك.

يقول الجميع: تسريبات كيدي. من يعلم - لماذا ؟! لا أحد!

المصدر المفتوح؟ يا للعار! آسف ولكن هذا صحيح بالنسبة لهذا الموقف.

ملاحظة: http://sourceware.org/bugzilla/show_bug.cgi؟id=14827

أنا أؤمن بك. سنتان ولا تحرك في هذا الجانب: /

أقول لإصلاح هذا الجانب وإلقاء اللوم بشكل كبير ، لأن التفرع هناك يبدو غير مجد.

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 يدويًا إذا كان lib متاحًا في النظام. نسميها في طريقة __del__() ، والتي يتم تنفيذها عندما يتم تجميع الكائن غير المرغوب فيه.

glibc.malloc.mxfast tunable في Glibc (https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html).

أعتقد أن هذا قد يكون السبب في أحد مشاريعنا ، لكن مستخدمينا يقومون بتشغيل Windows مع Python 3.8 الافتراضي (من الموقع الرسمي) مع تثبيت جميع التبعيات عبر النقطة. هل ستكون هذه المشكلة أيضًا على Windows؟ إذا كان الأمر كذلك ، فما هو المكافئ cdll.LoadLibrary("libc.so.6") ؟

تحرير: أجريت هذه الاختبارات الموصوفة هنا ، وكانت القمامة التي تم جمعها تؤدي وظيفتها بشكل صحيح في كل مرة:
https://github.com/pandas-dev/pandas/issues/21353
النظام: Windows 10
بايثون: 3.8.5
الباندا: 1.1.0

هل كانت هذه الصفحة مفيدة؟
1 / 5 - 1 التقييمات