使用set comprehension避免内存错误

时间:2013-03-02 06:53:12

标签: python memory-management set list-comprehension

我正在通过读取一个大数据集(~250k行)构建一个大型集合,为了提高效率,我决定使用set comprehension构造集合,但是我一直遇到内存错误,这很有意义,因为我相信垃圾收集器在评估集合理解期间不起作用。实际源代码如下:

def parsedData(filePath):
    with open(filePath, 'rb') as rawData:
        reader = csv.reader(rawData, delimiter=",")
        allJobs = {Job(rawJob(row)) for row in reader}
    return allJobs

有什么方法可以强制垃圾收集器清除它何时会出现内存错误?有更快的方法吗?我会使用lambda函数而不是set comprehension遇到相同的内存问题吗?

4 个答案:

答案 0 :(得分:1)

以下是可能发生这种情况的原因:

要创建一组唯一的自定义对象,您的对象必须至少实现__eq____hash__

演示:

class Obj(object):
    def __init__(self,val):
        self.val=val

    def __repr__(self):
        return self.val

li=['i am uniq','i am uniq','i am uniq','not really','not really','not really']        
print set(Obj(e) for e in li)

打印

set([i am uniq, i am uniq, not really, not really, i am uniq, not really])

现在添加所需的__eq____hash__方法:

class Obj(object):
    def __init__(self,val):
        self.val=val
        self.hash=hash(val)

    def __repr__(self):
        return self.val

    def __eq__(self,other):
        return self.hash==other.hash    

    def __hash__(self):
        return self.hash        

li=['i am uniq','i am uniq','i am uniq','really','really','really']        
print set(Obj(e) for e in li) 

打印:

set([i am uniq, really])

如果没有正确的哈希和eq,你可以看到你根本没有一套;你将拥有一个基于Jobs obj id的无序列表。换句话说,对于Python,即使将您定义为“相同”的对象也将被视为不同的对象,因为每个对象都有不同的obj id。这个'set'实际上会比等效列表大一点。


除此之外 - 更高效的内存方式可能​​是使用带有生成器的numpy中的structured array来逐行读取文件。

策略:

  1. 打开文件
  2. 确定文件的总行数 - 这是文件中最差的总记录
  3. 快退文件
  4. 读取第一条记录,并根据行数据(整数,浮点数,字节等)的记录确定numpy数组的最有效记录结构。这比Python对象中的等效密度要大得多。
  5. 快退文件
  6. 如果您想要哈希来统一数据,请将其添加到记录
  7. 预先将numpy数组分配给lines X records size
  8. 创建一个生成器,逐行读取文件,并将每个记录放在numpy数组中
  9. 不要添加重复记录(基于哈希)
  10. 调整numpy数组的大小以查找未添加的总重复记录...

答案 1 :(得分:1)

你说:

  

原始文件中不应该有任何重复项   铸造纯粹是为了使迭代更快

墨菲法律说会有重复。 "使迭代更快"在我看来,这似乎是过早和可疑的优化。

一些分类建议:

(0)在开始担心内存和CPU效率之前,在少量行上测试它以确保它正常工作(特别是确保Job对象可以清洗并且哈希方法真正反映了您对唯一性的定义)

(1)保持简单,即避免设置理解,发电机等......这样做是好事。一次制定一种方法。理解等是优雅的,但仅限于没有任何错误的用例......你正在处理半个文本文件。

(2)插入一些调试代码,以便您可以看到它在内存不足之前的距离。

(3)检查重复项!

def parsedData(filePath):
    allJobs = set()
    with open(filePath, 'rb') as rawData:
        reader = csv.reader(rawData, delimiter=",")
        for rownum, row in enumerate(reader, start=1):
            job = Job(rawJob(row))
            if job in allJobs:
                pass # add code to display dupe
            else:
                allJobs.add(job)
            if rownum % 10000 == 0:
                print rownum, "rows; ", len(allJobs), "unique"
    return allJobs

答案 2 :(得分:0)

<强>更新

正如@Hyperboreus指出的那样,这将抛出ValueError: I/O operation on closed file。我将它留在这里以防它有用。我认为解决这个问题的最佳方法可能是让文件保持打开状态,并使用生成器只在最后一刻读取数据。


试试这个:

def parsedData(filePath):
    with open(filePath, 'rb') as rawData:
        reader = csv.reader(rawData, delimiter=",")
        allJobs = (Job(rawJob(row)) for row in reader)
    return allJobs

它使用生成器表达式而不是set comprehension来创建和返回生成器。

来自python wiki

  

结果是使用发电机提高了性能   懒惰(按需)生成的值,转换为   降低内存使用率。此外,我们不需要等到所有的   在我们开始使用它们之前已经生成了元素。

答案 3 :(得分:0)

如果所有行都是唯一的,即使您使用列表推导而不是集合,也会出现MemoryError,那么您可以将该函数设置为生成器,以避免一次性加载所有作业:

def get_jobs(filePath):
    with open(filePath, 'rb') as rawData:
        reader = csv.reader(rawData, delimiter=",")
        for row in reader:
            yield Job(rawJob(row))

您可以将其用作:

for job in get_jobs(path):
    process(job)