لغز سيتم تصحيحه قريبًا:
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()
حسنًا ، هذا ، باختصار ، 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
التعليق الأكثر فائدة
للتسجيل ، نحن (+ @ sbneto) نستخدم هذا في الافتراض لبعض الوقت ، ونقوم بعمل جيد جدًا: