循环期间释放内存

11

我在我的代码中遇到了内存错误。我的解析器可以概括为:

# coding=utf-8
#! /usr/bin/env python
import sys
import json
from collections import defaultdict


class MyParserIter(object):

    def _parse_line(self, line):
        for couple in line.split(","):
            key, value = couple.split(':')[0], couple.split(':')[1]
            self.__hash[key].append(value)

    def __init__(self, line):
        # not the real parsing just a example to parse each
        # line to a dict-like obj
        self.__hash = defaultdict(list)
        self._parse_line(line)

    def __iter__(self):
        return iter(self.__hash.values())

    def to_dict(self):
        return self.__hash

    def __getitem__(self, item):
        return self.__hash[item]

    def free(self, item):
        self.__hash[item] = None

    def free_all(self):
        for k in self.__hash:
            self.free(k)

    def to_json(self):
        return json.dumps(self.to_dict())


def parse_file(file_path):
    list_result = []
    with open(file_path) as fin:
        for line in fin:
            parsed_line_obj = MyParserIter(line)
            list_result.append(parsed_line_obj)
    return list_result


def write_to_file(list_obj):
    with open("out.out", "w") as fout:
        for obj in list_obj:
            json_out = obj.to_json()
            fout.write(json_out + "\n")
            obj.free_all()
            obj = None

if __name__ == '__main__':
        result_list = parse_file('test.in')
        print(sys.getsizeof(result_list))
        write_to_file(result_list)
        print(sys.getsizeof(result_list))
        # the same result for memory usage result_list
        print(sys.getsizeof([None] * len(result_list)))
        # the result is not the same :(
目标是解析(大型)文件,将每行转换为JSON对象并写回到文件中。 我的目标是减少占用空间,因为在某些情况下,此代码会引发内存错误。在每次fout.write之后,我希望删除(释放内存)obj引用。 我尝试将obj设置为None或调用obj.free_all()方法,但它们都没有释放内存。我也使用了simplejson而不是json,这减少了占用空间,但在某些情况下仍然太大。 test.in看起来像:
test1:OK,test3:OK,...
test1:OK,test3:OK,...
test1:OK,test3:OK,test4:test_again...
....

你的test.in文件有多大? - YOU
@JonnyTieM,是的,我试过了,但对性能产生了可怕的负面影响。 - Ali SAID OMAR
你尝试过取消引用这些对象吗?在这里查看:https://dev59.com/TGUp5IYBdhLWcg3wLlRV - bipartite
虽然这段代码不是很大,但你可以在不使用任何类的情况下实现它,这样可以减少内存的使用。 - YOU
我实际上是在总结我的解析器,我的应用程序是基于https://en.wikipedia.org/wiki/Electronic_data_interchange文件解析器,采用插件模式(每个规范一个插件),解析规则取决于规范版本(在我的代码中,加载不同的解析器类)。 - Ali SAID OMAR
显示剩余15条评论
2个回答

4
不要在数组中存储过多的类实例,而是要内联执行。例如:
% cat test.in
test1:OK,test3:OK
test1:OK,test3:OK
test1:OK,test3:OK,test4:test_again

% cat test.py 
import json

with open("test.in", "rb") as src:
    with open("out.out", "wb") as dst:
        for line in src:
            pairs, obj = [x.split(":",1) for x in line.rstrip().split(",")], {}
            for k,v in pairs:
                if k not in obj: obj[k] = []
                obj[k].append(v)
            dst.write(json.dumps(obj)+"\n")

% cat out.out
{"test1": ["OK"], "test3": ["OK"]}
{"test1": ["OK"], "test3": ["OK"]}
{"test1": ["OK"], "test3": ["OK"], "test4": ["test_again"]}

如果速度较慢,请不要逐行写入文件,而是将转储的json字符串存储在数组中,并执行dst.write("\n".join(array))

谢谢,但这将意味着重构我的类并混合逻辑(输出到文件和解析输入)。解析器应该能够输出到文件或控制台,这就是为什么我需要存储解析的结果,以便能够迭代或获取键。最后,对于单元测试,这种方法不适合我。 - Ali SAID OMAR
@AliSAIDOMAR 这很简单。只需编写一个生成器,yield该值(基本上将此答案中的代码放入def中,并将dst.write替换为yield)。然后,您可以直接迭代结果并写入任何您想要的内容。 - Bakuriu
这里的主要观点是不要存储整个结果(以任何形式),而是逐行读取/解析/写入。您的原始程序的工作方式,内存消耗量为O(文件长度),而使用YOU的方法(逐行读取/解析/写入),内存消耗量为O(最大行长度)。 - hvb

2
为了使obj能够被释放,必须消除所有对它的引用。你的循环没有做到这一点,因为list_obj中的引用仍然存在。以下方法可以解决这个问题:
def write_to_file(list_obj):
    with open("out.out", "w") as fout:
        for ix in range(list_obj):
            obj = list_obj[ix]
            list_obj[ix] = None
            json_out = obj.to_json()
            fout.write(json_out + "\n")
            obj.free_all()

或者,您可以从list_obj的前面破坏性地弹出元素,尽管如果不得不太多次重新分配list_obj,这可能会导致性能问题。我没有尝试过这个,所以我不是很确定。该版本如下:

def write_to_file(list_obj):
    with open("out.out", "w") as fout:
        while len(list_obj) > 0:
            obj = list_obj.pop(0)
            json_out = obj.to_json()
            fout.write(json_out + "\n")
            obj.free_all()

谢谢。使用此代码的测试不会根据sys.getsizeof释放内存。 - Ali SAID OMAR
你尝试在消除引用后调用 gc.collect() 吗? - Tom Karzes
是的,我做到了,处理时间已经大大缩短。 - Ali SAID OMAR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接