1

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

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 函数而不是集合理解遇到相同的内存问题吗?

4

4 回答 4

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,您会发现根本没有集合;您将拥有一个基于Jobsobj id 的无序列表。换句话说,对于 Python,即使您定义为“相同”的对象也会被视为不同的对象,因为每个对象都有不同的 obj id。这个“集合”实际上会比等效列表大得多。


除此之外——一种更节省内存的方法可能是在 numpy 中使用结构化数组和生成器来逐行读取文件。

战略:

  1. 打开文件
  2. 确定文件的总行数——即文件中最坏情况的总记录数
  3. 倒带文件
  4. 读取第一条记录并根据行数据的记录(整数、浮点数、字节等)确定 numpy 数组的最有效记录结构这比 Python 对象中的等效数据要密集得多
  5. 倒带文件
  6. 如果您想要一个哈希来统一您的数据,请将其添加到记录中
  7. 将numpy数组预先分配给lines X records大小
  8. 创建一个生成器以逐行读取文件并将每条记录放在 numpy 数组中
  9. 不要添加重复记录(基于哈希)
  10. 为未添加的总重复记录调整 numpy 数组的大小...
于 2013-03-02T15:53:32.833 回答
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
于 2013-03-03T00:43:57.343 回答
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

它使用生成器表达式而不是集合推导来创建和返回生成器。

蟒蛇维基

使用生成器带来的性能改进是惰性(按需)生成值的结果,这转化为更低的内存使用量。此外,我们不需要等到所有元素都生成后才开始使用它们。

于 2013-03-02T07:20:56.140 回答
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)
于 2013-03-02T12:26:35.180 回答