【问题标题】:dataframe re-indexing object unnecessarily preserved in memory数据框重新索引对象不必要地保留在内存中
【发布时间】:2014-02-10 21:01:41
【问题描述】:

this question 之后,我实现了两个功能做同样的事情,一个使用重新索引,另一个不使用。第 3 行的功能不同:

def update(centroid):
    best_mean_dist = 200
    clust_members = members_by_centeriod[centroid]
    for member in clust_members:
        member_mean_dist = 100 - df.ix[member].ix[clust_members].score.mean()

        if member_mean_dist<best_mean_dist:
            best_mean_dist = member_mean_dist
            centroid = member
    return centroid,best_mean_dist

def update1(centroid):
    best_mean_dist = 200
    members_in_clust = members_by_centeriod[centroid]
    new_df = df.reindex(members_in_clust, level=0).reindex(members_in_clust, level=1)
    for member in members_in_clust:
        member_mean_dist = 100 - new_df.ix[member].ix[members_in_clust].score.mean()        

        if member_mean_dist<best_mean_dist:
            best_mean_dist = member_mean_dist
            centroid = member
    return centroid,best_mean_dist  

正在从 IPython 笔记本单元调用函数:

for centroid in centroids:
    centroid = [update(centroid) for centroid in centroids]

数据框df 是一个大型数据框,大约有 400 万行,占用大约 300MB 内存。

使用重新索引的update1 函数要快得多。但是,发生了一些意想不到的事情 - 在运行重新索引内存的几次迭代后,内存迅速从〜300MB上升到1.5GB,然后我得到内存违规。

update 函数不会出现这种行为。两件事我没有得到:

  1. 重新索引会生成副本,这很明显。但是每次 update1 功能完成时,该副本不是应该死吗? newdf 变量应该随着创建它的函数而死。对吗?

  2. 即使垃圾收集器没有立即杀死newdf,一个内存用完了,它也应该杀死它并且不会引发 outOfMemory 异常,对吧?

  3. 我尝试在 update1 函数末尾添加 del newdf 手动杀死 df,但没有帮助。那么这是否表明该错误实际上是在重新索引过程本身中?

编辑:

我发现了问题,但我无法理解这种行为的原因。它是 python 垃圾收集器,拒绝清理重新索引的数据帧。 这是有效的:

for i in range(2000):
   new_df = df.reindex(clust_members, level=0).reindex(clust_members, level=1)

这也是有效的:

def reindex():
    new_df = df.reindex(clust_members, level=0).reindex(clust_members, level=1)
    score  = 100 - new_df.ix[member].ix[clust_members].score.mean()
    return score

for i in range(2000):
    reindex()

这会导致重新索引对象保留在内存中:

z = []    
for i in range(2000):
    z.append(reindex()) 

我认为我的用法是天真的正确的。 newdf 变量如何与分数值保持联系,为什么?

【问题讨论】:

  • 请把你的 python 和 pandas 版本升级;您正在运行 64-但是对吗?你至少有 4GB 吧?
  • 运行 pandas '0.12.0' 32 位和 8GB 内存。但这似乎不是导致问题的重新索引。请参阅下面的评论..
  • 32 位限制为 2GB 的实际空间和 4GB 的总内存(额外的内存没有任何作用);实际上,您将很难分配> 1GB(因为通常python需要一个连续的空间)。你会在 64 位上取得更大的成功
  • 我发现了问题并编辑了问题(底部),您的想法将得到高度评价
  • 关于您的更新:您每次都在创建一个新对象,因为您持有对每个对象的引用,您的内存将无限增长。

标签: python pandas ipython ipython-notebook


【解决方案1】:

这是我的调试代码,当你做索引时,索引对象会创建_tuplesengine map,我认为这两个缓存对象使用了内存。如果我添加****标记的行,那么内存增加很小,在我的PC上大约6M:

import pandas as pd
print pd.__version__
import numpy as np
import psutil
import os
import gc

def get_memory():
    pid = os.getpid()
    p = psutil.Process(pid)
    return p.get_memory_info().rss

def get_object_ids():
    return set(id(obj) for obj in gc.get_objects())

m1 = get_memory()

n = 2000
iy, ix = np.indices((n, n))
index = pd.MultiIndex.from_arrays([iy.ravel(), ix.ravel()])
values = np.random.rand(n*n, 3)
df = pd.DataFrame(values, index=index, columns=["a","b","c"])

ix = np.unique(np.random.randint(0, n, 500))
iy = np.unique(np.random.randint(0, n, 500))

m2 = get_memory()
objs1 = get_object_ids()

z = []
for i in range(5):
    df2 = df.reindex(ix, level=0).reindex(iy, level=1)
    z.append(df2.mean().mean())
df.index._tuples = None    # ****
df.index._cleanup()        # ****
del df2
gc.collect()               # ****
m3 = get_memory()

print (m2-m1)/1e6, (m3-m2)/1e6

from collections import Counter

counter = Counter()
for obj in gc.get_objects():
    if id(obj) not in objs1:
        typename = type(obj).__name__
        counter[typename] += 1
print counter

【讨论】:

  • 此代码不会造成内存泄漏。我使用循环的 reidnexing 也不会造成内存泄漏。这也不是 ipython notebook 的问题,因为在 WING 中运行代码也会导致内存泄漏。所以它必须与我的用法或 python 垃圾收集器有关。我正在研究它,将不胜感激。
  • 我发现了问题并编辑了问题(底部),您的想法将得到高度评价
  • 谢谢,我会试试,让你知道。但是您对为什么数据框没有被垃圾收集器清理有任何见解吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-03
  • 2014-09-01
  • 2014-11-10
  • 2020-06-02
  • 2020-07-10
  • 2016-07-08
  • 1970-01-01
相关资源
最近更新 更多