内存使用:创建一个大集合与合并许多小集合

时间:2015-09-05 12:47:47

标签: python python-2.7 memory set

我使用%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))

2 个答案:

答案 0 :(得分:4)

Per the docsdef 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))accum_value, 需要大约447 MiB。 你可能会认为,因为这个iterable是一个生成器表达式,你将节省内存,但整数是saved in an internal free list

  

“For speed”,Python维护整数对象的内部空闲列表。不幸的是,这个免费清单既是不朽的,也是无限的。

因此,一旦实例化了每个集合,它所消耗的大部分内存都不会被释放。

返回值reduce也需要大约447 MiB。因此,对var do_array = new Array(); do_array[0] = "loot"; do_array[1] = "boo\"rua t"; do_array[2] = "srt"; for (var x=iFirst; x<=iLast; x++) { var s = "GET " + sBase.Replace("##", do_array[x]) + " HTTP/1.1\r\n\r\n"; } 的调用一起需要大约447 + 447 = 894 MiB。

答案 1 :(得分:3)

有很多误解需要澄清。首先,xrange not set中成为set(xrange(n))之前变为列表!

reduce方法的峰值内存来自set.union返回 new 值的事实,该值是2结果的并集集。 reduce在内部存储了一个额外的值accum_value。所以在for循环的最后一次迭代中:

accum_value = function(accum_value, x)

进入函数的accum_value包含90%的10⁷元素,x包含10%的10⁷元素,但返回值将需要所有10⁷元素的空间。所有这些空间需要同时保留。只有在function返回后,才会释放旧accum_valuex的内存。

然而,这还不是结束。峰值内存确实告诉了过程中需要多少内存,但是如果操作完成后还没有使用,如果该集合仍然存在的话。

因此需要一个不同的基准:

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方法不仅速度慢; 结果集也消耗了两倍的内存。这是因为一个集合由一个哈希表支持,并且只要认为它有太多的冲突就会变得更大。你遇到了病态行为,其中一组相同的值导致一个内存占另一个内存的两倍。