python源码阅读笔记之GC

引用计数法
增量:
各个对象的内部都有计数器。如果对象的引用数量增加,就在计数器上加1,否则减1
#define Py_INCREF(op) (                         \
    _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA       \
    ((PyObject*)(op))->ob_refcnt++)
下面的是含有NULL检查的宏    
#define Py_XINCREF(op) do { if ((op) == NULL) ; else Py_INCREF(op); } while (0)

计数器如何避免溢出?
#define PyObject_HEAD                   \
    _PyObject_HEAD_EXTRA                \
    Py_ssize_t ob_refcnt;               \
    struct _typeobject *ob_type;

从这里可以看出ob_refcnt的数据类型
在pyport.h
#ifdef HAVE_SSIZE_T
typedef ssize_t         Py_ssize_t;
#elif SIZEOF_VOID_P == SIZEOF_SIZE_T
typedef Py_intptr_t     Py_ssize_t;
#else
#   error "Python needs a typedef for Py_ssize_t in pyport.h."
#endif
是C里的ssize_t类型,这样就可以和各自CPU位数的指针的大小一样。
因为有符号位,所以只有一半的数值能用非负整数表示,为啥计数器要使用负数呢?这是为了debug
#define _Py_CHECK_REFCNT(OP)                                    \
{       if (((PyObject*)OP)->ob_refcnt < 0)                             \
                _Py_NegativeRefcount(__FILE__, __LINE__,        \
                                     (PyObject *)(OP));         \
}
减量操作
#define Py_DECREF(op)                                   \
    do {                                                \
        if (_Py_DEC_REFTOTAL  _Py_REF_DEBUG_COMMA       \
        --((PyObject*)(op))->ob_refcnt != 0)            \
            _Py_CHECK_REFCNT(op)                        \
        else                                            \
        _Py_Dealloc((PyObject *)(op));                  \
    } while (0)
先将计数器减量,如果得出0以外的数值就调用_Py_CHECK_REFCNT(),为了防止意外
否则调用_Py_Dealloc()
#define _Py_Dealloc(op) (                               \
    _Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA          \
    (*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))
tp_dealloc存储着释放各个对象的函数指针

大概的释放的调用如此
Py_DECREF
  _Py_Dealloc 
    tupledealloc
       PyObject_GC_Del
         PyObject_FREE
           PyObject_Free

插入计数处理
这些技术都是基于生成指向对象的引用时进行的,而这不一定是Python的对象
局部变量引用时,绝大多数情况都不用引用计数           
python终结器的概念:
内置数据类型的对象是不能设置终结器的,能定义终结器的只有用户创建的类

循环引用垃圾回收:
容器对象:可能保留了指向其他对象的引用的对象
这些对象都分配的用于循环引用垃圾回收的头结构体
/* GC information is stored BEFORE the object structure. */
typedef union _gc_head {
    struct {
        union _gc_head *gc_next;
        union _gc_head *gc_prev;
        Py_ssize_t gc_refs;
    } gc;
    long double dummy;  /* force worst-case alignment */
} PyGC_Head;

前两个用于双向链表,最后一个是用于复制
dummy的作用如源码注释:即使结构体gc的大小不合理。它也会将整个结构体PyGC_Head的大小对齐为long double型
由下述代码负责分配所有容器对象的函数
PyObject *
_PyObject_GC_Malloc(size_t basicsize)
{
    PyObject *op;
    PyGC_Head *g;
    if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))
        return PyErr_NoMemory();
    g = (PyGC_Head *)PyObject_MALLOC(
        sizeof(PyGC_Head) + basicsize);/*此处可见,在分配对象时也额外分配了用于循环引用垃圾回收的头大小*/
    if (g == NULL)
        return PyErr_NoMemory();
    g->gc.gc_refs = GC_UNTRACKED;/*GC_UNTRACKED存入用于循环引用垃圾回收的头内成员gc_refs,
    当出现这个标志时,GC会认为这个容器对象没有连接到对象链*/
    generations[0].count++; /* number of allocated GC objects */
    if (generations[0].count > generations[0].threshold &&
        enabled &&
        generations[0].threshold &&
        !collecting &&
        !PyErr_Occurred()) {
        collecting = 1;
        collect_generations();
        collecting = 0;
    }
    op = FROM_GC(g);/*这个会偏移用于循环垃圾引用的头的长度,返回正确的对象地址,
    为了不区分容器对象和其他对象*/
    return op;
}

#define GC_UNTRACKED                    _PyGC_REFS_UNTRACKED

gc_refs用负值做标志,这是为了节省空间
/* Get the object given the GC head */
#define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))

追踪容器对象
这个宏放在各个容器对象新建立的时候,负责连接链表的操作
/* Tell the GC to track this object.  NB: While the object is tracked the
 * collector it must be safe to call the ob_traverse method. */
#define _PyObject_GC_TRACK(o) do { \
    PyGC_Head *g = _Py_AS_GC(o); \    \*先从对象的开头地址开始,将头地址偏移相应的大小,
    取出用于循环引用垃圾回收的头*\
    if (g->gc.gc_refs != _PyGC_REFS_UNTRACKED) \
        Py_FatalError("GC object already tracked"); \
    g->gc.gc_refs = _PyGC_REFS_REACHABLE; \  \*修改gc_refs,表示可到达*\
    g->gc.gc_next = _PyGC_generation0; \ \*拿出了连接所有容器对象的全局性容器对象链表,把对象链接到这个链表*\
    g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; \
    g->gc.gc_prev->gc.gc_next = g; \
    _PyGC_generation0->gc.gc_prev = g; \
    } while (0);

#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)

同样的方式可以结束追踪对象;


/*** Global GC state ***/

struct gc_generation {
    PyGC_Head head;
    int threshold; /* collection threshold */
    int count; /* count of allocations or collections of younger
                  generations */
};
这个结构体用于管理各代的容器对象,一旦count超过了threshold程序就会对这一代执行GC

0代   生成的容器对象的数量 - 删除的容器对象的数量
1代   0代经过GC的次数
2代   1代经过GC的次数

/* linked lists of container objects */
static struct gc_generation generations[NUM_GENERATIONS] = {
    /* PyGC_Head,                               threshold,      count */
    {{{GEN_HEAD(0), GEN_HEAD(0), 0}},           700,            0},
    {{{GEN_HEAD(1), GEN_HEAD(1), 0}},           10,             0},
    {{{GEN_HEAD(2), GEN_HEAD(2), 0}},           10,             0},
};

PyGC_Head *_PyGC_generation0 = GEN_HEAD(0);
一开始的所有容器对象都连接着0代的对象

原文发布于微信公众号 - 鸿的学习笔记(shujuxuexizhilu)

原文发表时间:2017-07-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏jeremy的技术点滴

py3_cookbook_notes_01

3198
来自专栏xingoo, 一个梦想做发明家的程序员

【插件开发】—— 10 JFace开发详解

前几篇讲过SWT的使用,但是SWT是基本的小控件,只能针对使用简单的数据类型,比如字符串,数字等等。但是遇到了复杂的类,该怎么办呢? ?   不要担心!这...

3615
来自专栏Python

JavaScript基础

一 JavaScript的基础 1.1 JS的引入方式 1 直接编写 <script> alert('hello yuan') ...

2248
来自专栏GreenLeaves

C# String.Format的格式限定符与Format方法将多个对象格式化一个字符串原理

Format方法将多个对象格式化成一个字符串Format方法解析格式字符串的原理:

1022
来自专栏一个会写诗的程序员的博客

jQuery Validate自定义各种验证方法jQuery Validate自定义各种验证方法

892
来自专栏跟着阿笨一起玩NET

VB.NET自我总结语法

991
来自专栏mySoul

设计模式 里氏替换原则

在场景中,三毛需要什么枪支,就直接new 出一个枪支即可,然后其内通过抽象类获取到对象,然后对齐进行修饰

1166
来自专栏二进制文集

Thrift 对象序列化、反序列化-字节数组分析

本篇博客仅分析Thrift对象的序列化、反序列化的字节数组,以及Thrift对象的序列化、反序列化原理。其他源码分析会另开章节~

1592
来自专栏xingoo, 一个梦想做发明家的程序员

实用的JS代码段(表单篇)

整理了下比较实用的Javascript代码段,完整的代码参考 1 多个window.onload方法   由于onload方法时在页面加载完成后,自动调用...

2045
来自专栏菩提树下的杨过

java:POI导出excel

POI是一个开源项目,专用于java平台上操作MS OFFICE,企业应用开发中可用它方便导出Excel. 下面是使用示例: 1、maven中先添加依赖项 1 ...

2725

扫码关注云+社区