内存使用:创建一个大集合与合并许多小集合
Memory usage: creating one big set vs merging many small sets
我使用了 %memit
魔术函数来测量内存使用情况:
In [1]: %memit n = pow(10, 7); range(n)
peak memory: 568 MiB, increment: 272 MiB
In [2]: %memit n = pow(10, 7); set(xrange(n))
peak memory: 824 MiB, increment: 447 MiB
好的,所以似乎有一个中间步骤,其中 xrange(n)
被实例化为完整列表。但是,如果我将我的列表分成 10 个子列表,然后将它们一个接一个地合并呢?这样内存效率会更高,对吗?
In [3]: %memit n = pow(10, 7); reduce(set.union, (set(xrange(p, n, 10)) for p in range(10)))
peak memory: 1260 MiB, increment: 897 MiB
好吧,这并没有按预期进行。为什么 reduce
方法比 set(xrange(n))
消耗更多内存?
Per the docs,reduce
大致相当于:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
try:
initializer = next(it)
except StopIteration:
raise TypeError('reduce() of empty sequence with no initial value')
accum_value = initializer
for x in it:
accum_value = function(accum_value, x)
return accum_value
遍历 iterable
、(set(xrange(p, n, 10)) for p in range(10))
、
大约需要 447 MiB。
你可能会认为,因为这个可迭代对象是一个生成器表达式,所以你会节省内存,但整数是 saved in an internal free list:
“For speed”, Python maintains an internal free list for integer objects. Unfortunately, that free list is both immortal and unbounded in size.
所以一旦每个集合被实例化,它消耗的大部分内存就永远不会被释放。
返回值accum_value
也需要大约447 MiB。因此,对 reduce
的调用总共需要大约 447+447 = 894 MiB。
有很多误解需要澄清。首先,一个xrange
在set(xrange(n))
!
中变成set
之前,不是变成列表
reduce
方法的 峰值内存 来自以下事实:set.union
return 是 新 值是 2 个结果集的并集。 reduce
在内部存储了一个额外的值,即 accum_value
。所以在 for
循环的最后一次迭代中:
accum_value = function(accum_value, x)
进入函数的accum_value
包含90%的107个元素,x
包含10%的107个元素,但是return值需要space对于所有 10⁷ 个元素。 Space所有这些都需要同时保留。只有在 function
完成 returned 后,旧 accum_value
和 x
的内存才会释放。
然而这还没有结束。峰值内存确实说明了需要多少内存 in 进程,但不是正在使用多少内存 after 如果集合仍然存在,则操作完成存在。
因此需要不同的基准:
In [1]: %memit n = pow(10, 7); result = set(xrange(n));
peak memory: 522.22 MiB, increment: 498.93 MiB
In [2]: %memit 42
peak memory: 513.83 MiB, increment: 0.12 MiB
In [3]: import sys
In [4]: sys.getsizeof(result)
Out[4]: 268435688
和
In [1]: %memit n = pow(10, 7); result = reduce(set.union, (set(xrange(p, n, 10)) for p in range(10)));
peak memory: 1063.20 MiB, increment: 1039.71 MiB
In [2]: %memit 42
peak memory: 779.77 MiB, increment: 0.00 MiB
In [3]: import sys
In [4]: sys.getsizeof(result)
Out[4]: 536871144
In [5]: 536871144.0 / 268435688
Out[5]: 1.9999991357333977
因此 reduce
执行后的内存使用量下降 至 778 MiB;然而,它仍然比更简单的集合构造案例要多。正如您所看到的,set(xrange(n))
不需要太多内部内存,因为在构建集合后内存使用量仅下降了 9 MiB。
最值得注意的是,不仅 reduce
方法速度较慢; 结果集也消耗两倍的内存。这是因为一个集合由哈希表支持,并且只要认为它有太多冲突,它就会变得更大。您遇到了病态行为,其中一组相同的值导致一个占用的内存是另一个的两倍。
我使用了 %memit
魔术函数来测量内存使用情况:
In [1]: %memit n = pow(10, 7); range(n)
peak memory: 568 MiB, increment: 272 MiB
In [2]: %memit n = pow(10, 7); set(xrange(n))
peak memory: 824 MiB, increment: 447 MiB
好的,所以似乎有一个中间步骤,其中 xrange(n)
被实例化为完整列表。但是,如果我将我的列表分成 10 个子列表,然后将它们一个接一个地合并呢?这样内存效率会更高,对吗?
In [3]: %memit n = pow(10, 7); reduce(set.union, (set(xrange(p, n, 10)) for p in range(10)))
peak memory: 1260 MiB, increment: 897 MiB
好吧,这并没有按预期进行。为什么 reduce
方法比 set(xrange(n))
消耗更多内存?
Per the docs,reduce
大致相当于:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
try:
initializer = next(it)
except StopIteration:
raise TypeError('reduce() of empty sequence with no initial value')
accum_value = initializer
for x in it:
accum_value = function(accum_value, x)
return accum_value
遍历 iterable
、(set(xrange(p, n, 10)) for p in range(10))
、
大约需要 447 MiB。
你可能会认为,因为这个可迭代对象是一个生成器表达式,所以你会节省内存,但整数是 saved in an internal free list:
“For speed”, Python maintains an internal free list for integer objects. Unfortunately, that free list is both immortal and unbounded in size.
所以一旦每个集合被实例化,它消耗的大部分内存就永远不会被释放。
返回值accum_value
也需要大约447 MiB。因此,对 reduce
的调用总共需要大约 447+447 = 894 MiB。
有很多误解需要澄清。首先,一个xrange
在set(xrange(n))
!
set
之前,不是变成列表
reduce
方法的 峰值内存 来自以下事实:set.union
return 是 新 值是 2 个结果集的并集。 reduce
在内部存储了一个额外的值,即 accum_value
。所以在 for
循环的最后一次迭代中:
accum_value = function(accum_value, x)
进入函数的accum_value
包含90%的107个元素,x
包含10%的107个元素,但是return值需要space对于所有 10⁷ 个元素。 Space所有这些都需要同时保留。只有在 function
完成 returned 后,旧 accum_value
和 x
的内存才会释放。
然而这还没有结束。峰值内存确实说明了需要多少内存 in 进程,但不是正在使用多少内存 after 如果集合仍然存在,则操作完成存在。
因此需要不同的基准:
In [1]: %memit n = pow(10, 7); result = set(xrange(n));
peak memory: 522.22 MiB, increment: 498.93 MiB
In [2]: %memit 42
peak memory: 513.83 MiB, increment: 0.12 MiB
In [3]: import sys
In [4]: sys.getsizeof(result)
Out[4]: 268435688
和
In [1]: %memit n = pow(10, 7); result = reduce(set.union, (set(xrange(p, n, 10)) for p in range(10)));
peak memory: 1063.20 MiB, increment: 1039.71 MiB
In [2]: %memit 42
peak memory: 779.77 MiB, increment: 0.00 MiB
In [3]: import sys
In [4]: sys.getsizeof(result)
Out[4]: 536871144
In [5]: 536871144.0 / 268435688
Out[5]: 1.9999991357333977
因此 reduce
执行后的内存使用量下降 至 778 MiB;然而,它仍然比更简单的集合构造案例要多。正如您所看到的,set(xrange(n))
不需要太多内部内存,因为在构建集合后内存使用量仅下降了 9 MiB。
最值得注意的是,不仅 reduce
方法速度较慢; 结果集也消耗两倍的内存。这是因为一个集合由哈希表支持,并且只要认为它有太多冲突,它就会变得更大。您遇到了病态行为,其中一组相同的值导致一个占用的内存是另一个的两倍。