【问题标题】:Why does one version leak memory but not the other ? (Python)为什么一个版本会泄漏内存,而另一个不会? (Python)
【发布时间】:2012-11-17 15:46:06
【问题描述】:

这两个函数以基本相同的方式计算相同的东西(整数的个数,使得关联的 Collat​​z 序列的长度不大于 n)。唯一的区别是第一个只使用集合,而第二个同时使用集合和列表。

第二个泄漏内存(至少在 Python 3.2 的 IDLE 中),第一个没有,我不知道为什么。我尝试了一些“技巧”(例如添加del 语句),但似乎没有任何帮助(这并不奇怪,这些技巧应该没用)。

如果有人能帮助我了解发生了什么,我将不胜感激。

如果您想测试代码,您可能应该使用 55 到 65 范围内的 n 值,任何高于 75 的值几乎肯定会导致(完全预期的)内存错误。

def disk(n):
    """Uses sets for explored, current and to_explore. Does not leak."""
    explored = set()
    current = {1}
    for i in range(n):
        to_explore = set()
        for x in current:
            if not (x-1) % 3 and ((x-1)//3) % 2 and not ((x-1)//3) in explored:
                to_explore.add((x-1)//3)
            if not 2*x in explored:
                to_explore.add(2*x)
        explored.update(current)
        current = to_explore
    return len(explored)

def disk_2(n):
    """Does exactly the same thing, but Uses a set for explored and lists for
        current and to_explore. 
       Leaks (like a sieve :))
    """
    explored = set()
    current = [1]
    for i in range(n):
        to_explore = []
        for x in current:
            if not (x-1) % 3 and ((x-1)//3) % 2 and not ((x-1)//3) in explored:
                to_explore.append((x-1)//3)
            if not 2*x in explored:
                to_explore.append(2*x)
        explored.update(current)
        current = to_explore
    return len(explored)

EDIT :在使用解释器的交互模式(没有 IDLE)时也会发生这种情况,但在直接从终端运行脚本时不会发生这种情况(在这种情况下,内存使用量会恢复正常)函数返回后的时间,或在明确调用 gc.collect() 后的时间。

【问题讨论】:

  • 如何测量内存泄漏?您是否尝试在 IDLE 等交互式环境之外检查它?
  • 显示您的泄漏测量值。在这两种情况下,您都在分配列表和集合,在这两种情况下,所有这些对象都可以通过激活函数来访问,因此它们不会被垃圾回收。
  • @Marcin :问题不在于函数执行时的内存使用情况。这与您所期望的差不多(上升趋势,当currentto_explore 的某些旧版本被垃圾收集时偶尔会下降)。问题是在disk_2 返回后内存没有被释放(即使你明确地调用gc.collect())。对于 n=65,使用量峰值为 800MB,并在 disk_2 返回后保持在 ~200MB。
  • @user36732 “内存未释放”是什么意思?你的意思是过程映像不会占用更少的内存吗?如果是这样,那可能是因为您在 linux 上运行它,对吧?
  • 1) 如果您正在运行 CPython,则 gc.collect() 在这里无关紧要,因为您不会创建任何引用循环(这些只是 set()list() 与数字对吗?)。在这种情况下,一旦对象失去最后一个引用,CPython 就会“收集”对象。 2)我们仍然不知道您如何衡量内存使用情况,这可能是问题的症结所在。

标签: python python-3.x


【解决方案1】:

CPython allocates small objects (obmalloc.c, 3.2.3) 在它管理的 256 KiB 块中称为 arenas 的 4 KiB 池中。每个活动池都有一个固定的块大小,范围从 8 字节到 256 字节,步长为 8。例如,从具有 16 字节块大小的第一个可用池中分配一个 14 字节对象。

如果在堆上分配 arena 而不是使用 mmap(这可以通过 mallopt's M_MMAP_THRESHOLD 调整),则存在一个潜在问题,因为堆不能缩小到分配的最高 arena 以下,只要 1 个块就不会释放in 1 pool 被分配给一个对象(CPython 不会在内存中浮动对象)。

鉴于上述情况,您的函数的以下版本应该可以解决问题。将 return len(explored) 行替换为以下 3 行:

    result = len(explored)
    del i, x, to_explore, current, explored
    return result + 0

在释放容器和所有引用的对象(将 arena 释放回系统)后,这将返回一个新的 int,其表达式为 result + 0。只要有对第一个结果对象的引用,堆就不能收缩。在这种情况下,当函数返回时会自动释放。

如果您在没有“加 0”步骤的情况下以交互方式对此进行测试,请记住 REPL(读取、评估、打印、循环)保留对可通过伪变量“_”访问的最后一个结果的引用。

在 Python 3.3 中,这应该不是问题,因为对象分配器已修改为 use anonymous mmap for arenas,如果可用。 (对象分配器的上限也被提高到 512 字节以适应 64 位平台,但这在这里无关紧要。)

关于手动垃圾回收,gc.collect() 会完整收集跟踪的容器对象,但它也会clears freelists 收集由内置类型(例如框架、方法、浮点数)维护的对象。 Python 3.3 添加了额外的 API 函数来清除列表 (PyList_ClearFreeList)、字典 (PyDict_ClearFreeList) 和集合 (PySet_ClearFreeList) 使用的空闲列表。如果您希望保持空闲列表不变,请使用gc.collect(1)

【讨论】:

    【解决方案2】:

    我怀疑它会泄漏,我敢打赌只是垃圾收集还没有开始,所以使用的内存一直在增长。这是因为每一轮外循环,前一个当前列表都可以被垃圾回收,但直到任何时候都不会被垃圾回收。

    此外,即使是垃圾回收,内存通常也不会释放回操作系统,因此您必须使用任何 Python 方法来获取当前使用的堆大小。

    如果您在每次外部循环迭代结束时添加垃圾收集,这可能会减少内存使用量,也可能不会,这取决于 Python 如何在没有它的情况下处理其堆和垃圾收集。

    【讨论】:

    • 当我说它泄漏时,我的意思是在函数返回后内存使用不会恢复正常(即使你显式调用 gc.collect())。
    • 即使内存在gc.collect() 调用后没有恢复到“正常”状态,但这并不意味着存在泄漏,除非总体上它不断变大,一遍又一遍地做这件事。跨度>
    • @user36732 您是在查看从操作系统保留的内存,还是什么?即使内存被垃圾收集并成为未分配的堆内存,内存也不会释放回操作系统。
    • @hyde 将列表添加到集合中是错误的 - 我看到集合是 updated,因此根本没有将列表添加到集合中。当一个项目added 到一个集合时,对该项目的引用存储在该集合中。这就是add 所做的基本工作。
    • @Marcin 为什么explored 会包含对current 的引用? some_set.update(some_list) 应该等同于 some_set.update(set(some_list)),对吧?
    【解决方案3】:

    您没有内存泄漏。 linux 上的进程在退出之前不会向操作系统释放内存。因此,您将在例如top 只会上升。

    只有在运行相同或更小的作业后,Python 从操作系统中获取更多内存,而当它“应该”能够重用它用于“应该”的对象的内存时,才会出现内存泄漏已被垃圾回收。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多