python - 为什么一个版本会泄漏内存而另一个不会? (Python)

标签 python python-3.x

这两个函数以基本相同的方式计算相同的东西(整数的数量,使得相关的 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 :当使用解释器的交互模式(没有空闲)时也会发生这种情况,但当直接从终端运行脚本时不会发生(在这种情况下,内存使用会恢复正常一些函数返回后的时间,或对 gc.collect() 的显式调用后的时间。

最佳答案

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

如果在堆上分配竞技场而不是使用 mmap(这可以通过 mallopt's M_MMAP_THRESHOLD 进行调整),则存在一个潜在的问题,因为堆不能收缩到最高分配的竞技场以下,只要 1 个 block 就不会释放在 1 个池中分配给一个对象(CPython 不会在内存中 float 对象)。

鉴于上述情况,您的功能的以下版本应该可以解决问题。将 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由内置类型(例如框架、方法、 float )维护的对象的集合。 Python 3.3 添加了额外的 API 函数来清除列表(PyList_ClearFreeList)、字典(PyDict_ClearFreeList)和集合(PySet_ClearFreeList)使用的空闲列表。如果您希望完整保留空闲列表,请使用 gc.collect(1)

关于python - 为什么一个版本会泄漏内存而另一个不会? (Python),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13432103/

相关文章:

python - 值错误 : too many values to unpack (expected 2)

python - transient 输入窗口

python - Django 加权百分比

python - 将列表中的单词打印到 pygame 中的特定坐标

Python 可能的舍入错误?

python - pandas to_csv ,唯一记录数量减少

python - 如何从链接列表中抓取?

python - 按索引遍历数据框

python - 删除重复的 Python 循环链表

python - TfidfVectorizer 的词汇表和 get_features() 之间的区别?