【问题标题】:Memory usage: creating one big set vs merging many small sets内存使用:创建一个大集合与合并许多小集合
【发布时间】:2015-12-01 12:10:50
【问题描述】:

我使用了%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)) 消耗更多内存?

【问题讨论】:

  • 相关问题:stackoverflow.com/questions/15198042/… 注意set.union 将使用 more 内存,如果参数是一个集合,因为它假定公共元素很少,因此它将分配双倍所需的内存。

标签: python python-2.7 memory set


【解决方案1】:

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

“为了速度”,Python 为整数对象维护了一个内部空闲列表。不幸的是,这个免费列表是不朽的,而且大小是无限的。

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

返回值accum_value 也需要大约 447 MiB。因此,对reduce 的调用总共需要大约 447+447 = 894 MiB。

【讨论】:

    【解决方案2】:

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

    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 方法更慢; 结果集也消耗两倍的内存。这是因为一个集合是由一个哈希表支持的,当它被认为有太多的冲突时,它就会变得更大。您遇到过病态行为,即一组相同的值导致一个占用的内存是另一个的两倍。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-30
      • 2021-09-30
      • 1970-01-01
      • 2012-07-15
      • 2019-02-11
      • 1970-01-01
      • 2012-10-24
      • 1970-01-01
      相关资源
      最近更新 更多