Pandas: 循环GC问题

创建于 2013-01-08  ·  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()

这里的对象仅在循环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无关,但是可以证实这一点。

是的,这似乎可以解决问题。 在IPython中运行它后,内存使用为450MB,然后malloc_trim释放了400MB。 很恶毒

malloc_trim领先之后,这看起来像是glibc优化出现了问题。
外部参照:
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

那不会解决。 也许有一天我们应该向熊猫添加一些辅助函数来进行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 PC和服务器的所有环境。 没别的了!!!

我知道,你会说我:好的,写一个补丁! 我会做的(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问题似乎出在glibcmalloc代码中。 显然,那里的free实现不尊重在某个阈值后应该发出malloc_trim的标志,正如您在@ghost的链接中看到的malloc_trim且内存泄漏。 如果库在系统中可用,我们所做的只是手动调用malloc_trim 。 我们用__del__()方法调用它,该方法在对象被垃圾回收时执行。

glibc.malloc.mxfast可调参数已在Glibc(https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html)中引入。

我认为这可能是我们项目中的罪魁祸首,但是我们的用户使用默认的Python 3.8(来自官方网站)运行Windows,并且所有依赖项都通过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 等级