加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Python > 正文

为什么一个版本泄漏内存但不泄漏另一个版本? (Python)

发布时间:2020-12-20 13:31:19 所属栏目:Python 来源:网络整理
导读:这两个函数以基本相同的方式计算相同的事物(整数的数量,使得相关的Collat??z序列的长度不大于n).唯一的区别是第一个使用集合,而第二个使用集合和列表. 第二个泄漏内存(在IDLE中使用Python 3.2,至少),第一个没有,我不知道为什么.我尝试了一些“技巧”(例如添
这两个函数以基本相同的方式计算相同的事物(整数的数量,使得相关的Collat??z序列的长度不大于n).唯一的区别是第一个使用集合,而第二个使用集合和列表.

第二个泄漏内存(在IDLE中使用Python 3.2,至少),第一个没有,我不知道为什么.我尝试了一些“技巧”(例如添加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)

编辑:这也发生在使用解释器的交互模式(没有IDLE)时,而不是直接从终端运行脚本时(在这种情况下,内存使用在函数返回后的某个时间恢复正常,或者很快因为有一个显式调用gc.collect()).

解决方法

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

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

鉴于上述情况,您的函数的以下版本应该可以解决问题.用以下3行替换行返回len(探索过):

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

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

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

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

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读