Skip to content

Latest commit

 

History

History
56 lines (31 loc) · 4.22 KB

More.md

File metadata and controls

56 lines (31 loc) · 4.22 KB

Python 垃圾回收机制

Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。

  1. 引用计数

    PyObject是每个对象必有的内容,其中ob_refcnt就是作为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少。引用计数为0时,该对象生命就结束了。

    优点:简单、实时性
    缺点:维护引用计数消耗资源、循环引用

  2. 标记-清除机制

    基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。

  3. 分代技术

    分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。Python默认定义了三代对象集合,索引数越大,对象存活时间越长。

    当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。

浅拷贝、深拷贝

首先需要搞清楚两个概念:赋值和引用,对于操作 target = source:

  • 赋值操作:程序先新建对象target,然后将source的值拷贝到target中。这里,target和source值相同,但是它们是两个完全不同的对象。
  • 引用操作:程序直接将target指向source,也就是说target和source是同一个对象,target只不过是source的一个别名。

python中没有赋值,只有引用。如果我们想拷贝一个对象,而不仅仅是创建一个引用,那么该如何操作呢?万能的python提供了两种拷贝机制浅拷贝(shallow copy)、深拷贝(deep copy)供我们选择,浅拷贝和深拷贝的唯一区别在于对嵌套对象的拷贝处理上。

对于嵌套对象比如说source = [1, 2, [3, 4]],浅拷贝创建新的列表对象target,target中的所有元素均是source中元素的引用,也就是说target中的元素只是source中元素的别名。切片操作[start:end]属于浅拷贝。

深拷贝,其实就是递归拷贝。也就是说对于嵌套对象比如说source = [1, 2, [3, 4]],深拷贝时创建新的列表对象target,然后递归地将source中的所有对象均拷贝到target中。即如果source中的元素是列表、字典等,那么python将拷贝这些列表、字典中的对象到target中去,就这样迭代下去,直到不存在嵌套结构。

list 实现机制

类似于 C++ STL中 vector 的实现。在需要的时候扩容,但又不允许过度的浪费,适当地进行内存回收。

空间不够,或者利用率小于 50% 时,用下面计算方式重新分配空间:

new_allocated = (newsize // 8) + (newsize < 9 and 3 or 6)
# 调整后大小 (new_allocated) = 新元素数量 (newsize) + 预留空间 (new_allocated)
  1. 当 newsize >= allocated,自然按照这个新的长度 "扩容" 内存。

  2. 如果 newsize < allocated,且利用率低于一半:

    allocated newsize new_size + new_allocated
    10 4 4 + 3
    20 9 9 + 7

更多阅读

Python中list的实现