最近在在处理 XML 文件,一个就十几个 G ,很容易爆内存。我是用的 generator 爬取 XML 的,按理说不应该每一步之后,自动清理 element 的内存,内存中每次只保存一个 element 吗?于是联想到一个问题
for i in range(10**20): s = i + 10
range 函数本身就是生成器,为什么做上面的计算,内存占用就几乎不会变动,但是如果改成下面的代码:
for i in range(10**20): print(i)
内存很快就爆了 想请教大佬们,这二者有什么区别呢?谢谢大家!
1
westoy 2022-07-09 11:50:00 +08:00
下面的有 IO 输出
|
2
liyifu1994 OP @westoy 谢谢大佬,爬取 XML 文件的时候,也需要 append 到 list ,所以就需要手工清理一遍,也就是 elem.clear()。只有不输出的话,generator 相对于 list 的优势才是内存管理,我理解的对吧?
|
3
haoliang 2022-07-09 12:19:59 +08:00
我不相信 `for i in range(10**20): print(i)` 会占满内存。尽管 sys.stdout 是 buffered ,但 buffer 一般也就 8k
|
4
weiwoxinyou 2022-07-09 23:31:36 +08:00 1
如#3 所说,单纯 `print` 不会导致内存占满,我感觉你需要检查的是是不是一直对同一个列表或其他变量填充数据且不断进行变量新增,建议放完整代码。
题中两者区别是是否存在 IO ,但是 Python 默认采用单线程,所以第二个会慢一点,但是绝不会因此导致内存占满。 |
5
winglight2016 2022-07-10 07:13:02 +08:00 1
generator 不是万能药。read file line by line 只是保证文件不是一次性读取到内存,你还得确保没有用全局变量保存在内存,同时写入文件的对象也不是整个文件。
最后,你真的搞明白 generator 了吗? range 的内部实现就是 generator |
6
liyifu1994 OP 完整代码如下:
def parse_and_remove(filename, path): path_parts = path.split('/') doc = iterparse(filename, ('start', 'end')) # Skip the root element next(doc) tag_stack = [] elem_stack = [] for event, elem in doc: if event == 'start': tag_stack.append(elem.tag) elem_stack.append(elem) elif event == 'end': if tag_stack == path_parts: yield elem elem.clear() ##这里加上 elem.clear(),内存占用几乎不变,可以完成任务;如果去掉的话,内存占用会越来越大直至 crash try: tag_stack.pop() elem_stack.pop() except IndexError: pass def count_stats(filepath, filename): start = time.time() print('\nStart processing file ', filepath) data = parse_and_remove(filepath, 'Job') results = [] count = 1 for job_node in data: continue ##即使这里什么都不做,内存占用也会越来越大 print 的程序如主楼所示。内存占用越来越大直至 crash 谢谢二位 @winglight2016 @weiwoxinyou |
7
liyifu1994 OP @weiwoxinyou
@winglight2016 好像跟 jupyter 有关系。我用的是 vscode 和 jupyter interactive 去 print 数据,如果放在 CMD 里运行,是不会占用内存的。 |