Python itertools.chain()是否允许从已消耗的项目中释放内存
我试图通过成批运行大型查询来减少Django应用程序中的内存使用。尽管我认为自己很聪明,但内存使用量一直在增长,直到进程最终被终止。我目前有一个理论认为查询集不会被垃圾收集,我想知道这是否与我如何批处理查询有关Python itertools.chain()是否允许从已消耗的项目中释放内存,python,memory,iteration,itertools,Python,Memory,Iteration,Itertools,我试图通过成批运行大型查询来减少Django应用程序中的内存使用。尽管我认为自己很聪明,但内存使用量一直在增长,直到进程最终被终止。我目前有一个理论认为查询集不会被垃圾收集,我想知道这是否与我如何批处理查询有关 def grouper(iterable, n, fillvalue=None): "Collect data into fixed-length chunks or blocks" args = [iter(iterable)] * n return zip_l
def grouper(iterable, n, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
def get_data(pks):
groups = list(grouper(pks, batch_size))
querysets = [_queryset(pks) for pks in groups]
return itertools.chain(*querysets)
for item in get_data([..big list of primary keys..]):
process(item)
假设在get\u data
函数中生成了3个查询集。当我消耗完第一个查询集的所有项目后,该查询集会被释放吗?或者从技术上说我还从连锁店那里得到了它的参考
我不确定内存是否保存在这里(它可能是数据库驱动程序,Django本身内部的东西,等等),但它似乎是一个不错的候选者。有没有好的工具可以测量对象类型的内存使用情况?这段特定的代码目前正在Python2上运行
我应该注意到我是从ipython外壳上运行的,以防万一
编辑:
看来链在这里不负责任。我添加了一些代码来打印每个类的对象计数,并且模型对象的计数保持不变
import gc
def get_object_counts():
from collections import Counter
classes = []
for obj in gc.get_objects():
if hasattr(obj, '__class__'):
classes.append(str(obj.__class__))
return Counter(classes)
然后在特定的时间间隔(批量大小):
为了完成,这里是前9名。我认为罪魁祸首是django.db.models.base.ModelState
,它一直在增长,没有被收集
第一:
("<type 'dict'>", 59184)
("<type 'list'>", 48710)
("<type 'function'>", 48300)
("<type 'tuple'>", 38920)
("<type 'cell'>", 10203)
("<type 'weakref'>", 9957)
("<type 'set'>", 7230)
("<type 'type'>", 5947)
("<class 'django.db.models.base.ModelState'>", 4682)
(“”,59184)
("", 48710)
("", 48300)
("", 38920)
("", 10203)
("", 9957)
("", 7230)
("", 5947)
("", 4682)
第二:
("<type 'dict'>", 59238)
("<type 'list'>", 48730)
("<type 'function'>", 48315)
("<type 'tuple'>", 38937)
("<type 'cell'>", 10207)
("<type 'weakref'>", 9959)
("<type 'set'>", 7230)
("<type 'type'>", 5950)
("<class 'django.db.models.base.ModelState'>", 4696)
(“”,59238)
("", 48730)
("", 48315)
("", 38937)
("", 10207)
("", 9959)
("", 7230)
("", 5950)
("", 4696)
不确定,但您可以通过将临时列表转换为iterables/Generator来避免创建临时列表(并在源位置解决分配问题):
def get_data(pks):
groups = grouper(pks, batch_size) # turn off explicit list conversion
querysets = (_queryset(pks) for pks in groups) # gencomp not listcomp
return itertools.chain.from_iterable(querysets) # nicer with "from_iterable"
我自己也看到过类似的行为,似乎
itertools.chain
(和itertools.chain.from_iterable
)会保留对作为参数传递的任何内容的引用,直到它们停止迭代。我想这就是django查询集及其缓存结果没有被垃圾收集的原因。这似乎是python2和python3上的行为,也是您从任何用户定义的python函数中看到的行为(请参阅)。也许C语言库中的函数(如itertools)在退出之前可以更自由地删除对参数的引用,但显然它们并不选择这样做
作为一种解决方法,您可以通过使用iter
或itertools.chain
本身,在迭代器中将各个参数包装到itertools.chain
。看起来,一旦这些迭代器耗尽,它们就会删除对底层iterable的引用,并允许对其进行垃圾收集
最后要注意的是,即使完全使用结果也不足以释放内存——在释放迭代器(或子迭代器)使用的内存之前,必须将控件再次返回给迭代器(或链中的任何子迭代器)。同样,这也是您从普通python函数中所期望的
下面的代码显示了这一切的发生:
from __future__ import print_function
import itertools
import gc
def print_whats_left_after(num, numbers_iter):
""" Read three numbers and print what pairs haven't been gc'd """
for _ in range(num):
next(numbers_iter, None)
gc.collect()
# Print integer pairs that were not garbage collected
print(sorted([o for o in gc.get_objects()
if isinstance(o, list) and len(o) == 2 and
all(isinstance(i, int) for i in o)]))
print_whats_left_after(2, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(4, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(5, itertools.chain([1, 2], [3, 4]))
# -> []
print_whats_left_after(2, itertools.chain.from_iterable([[1, 2], [3, 4]]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain.from_iterable([[1, 2], [3, 4]]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(2, itertools.chain(itertools.chain([1, 2]), [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain(itertools.chain([1, 2]), [3, 4]))
# -> [[3, 4]] # [1, 2] was gc'd!!!
print_whats_left_after(2, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[3, 4]] # [1, 2] was gc'd!!!
print_whats_left_after(4, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[3, 4]]
print_whats_left_after(5, itertools.chain(iter([1, 2]), [3, 4]))
# -> []
def arg_clobberer(arg):
arg = None
yield
print_whats_left_after(0, arg_clobberer([1, 2]))
# -> [[1, 2]]
print_whats_left_after(1, arg_clobberer([1, 2]))
# -> []
def arg_deleter(arg):
del arg
yield
print_whats_left_after(0, arg_deleter([1, 2]))
# -> [[1, 2]]
print_whats_left_after(1, arg_deleter([1, 2]))
# -> []
希望这有帮助
grouper
函数取自itertools recipes doc:listcomp的范围是否与[]*n相同?对于不可变对象是的,但我的构造会为每个元素生成一个不同的iterable。也许我错了,特别是如果它是来自食谱的代码。暂时别提了。谢谢,这真是有用的信息。将参数包装到其他生成器中的好技巧。我最终重构了原始代码,因此无法对其进行实际测试,但我认为您已经充分回答了问题。
from __future__ import print_function
import itertools
import gc
def print_whats_left_after(num, numbers_iter):
""" Read three numbers and print what pairs haven't been gc'd """
for _ in range(num):
next(numbers_iter, None)
gc.collect()
# Print integer pairs that were not garbage collected
print(sorted([o for o in gc.get_objects()
if isinstance(o, list) and len(o) == 2 and
all(isinstance(i, int) for i in o)]))
print_whats_left_after(2, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(4, itertools.chain([1, 2], [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(5, itertools.chain([1, 2], [3, 4]))
# -> []
print_whats_left_after(2, itertools.chain.from_iterable([[1, 2], [3, 4]]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain.from_iterable([[1, 2], [3, 4]]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(2, itertools.chain(itertools.chain([1, 2]), [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain(itertools.chain([1, 2]), [3, 4]))
# -> [[3, 4]] # [1, 2] was gc'd!!!
print_whats_left_after(2, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[1, 2], [3, 4]]
print_whats_left_after(3, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[3, 4]] # [1, 2] was gc'd!!!
print_whats_left_after(4, itertools.chain(iter([1, 2]), [3, 4]))
# -> [[3, 4]]
print_whats_left_after(5, itertools.chain(iter([1, 2]), [3, 4]))
# -> []
def arg_clobberer(arg):
arg = None
yield
print_whats_left_after(0, arg_clobberer([1, 2]))
# -> [[1, 2]]
print_whats_left_after(1, arg_clobberer([1, 2]))
# -> []
def arg_deleter(arg):
del arg
yield
print_whats_left_after(0, arg_deleter([1, 2]))
# -> [[1, 2]]
print_whats_left_after(1, arg_deleter([1, 2]))
# -> []