Rawrefcount 和 GC

GC 接口

“PyObject” 是一个原始结构,至少包含两个字段,ob_refcnt 和 ob_pypy_link。ob_refcnt 是在 CPython 上使用的引用计数。如果 PyObject 结构链接到一个活动的 PyPy 对象,则其当前地址存储在 ob_pypy_link 中,并且 ob_refcnt 通过常量 REFCNT_FROM_PYPY 或常量 REFCNT_FROM_PYPY_LIGHT (== REFCNT_FROM_PYPY + SOME_HUGE_VALUE) (表示“轻量级终结器”) 增加。

大多数 PyPy 对象存在于 cpyext 之外,反之,在 cpyext 中,可能存在许多 PyObject,而 PyPy 的其他部分无法看到它们。然而,在接口处,我们可以“链接”一个 PyPy 对象和一个 PyObject。有两种类型的链接

rawrefcount.create_link_pypy(p, ob)

在现有的对象 gcref ‘p’ 和新分配的 PyObject 结构 ‘ob’ 之间建立链接。ob->ob_refcnt 必须初始化为 REFCNT_FROM_PYPY 或 REFCNT_FROM_PYPY_LIGHT。(第二种情况是一种优化:当 GC 发现 PyPy 对象和 PyObject 不再被引用时,它可以简单地释放() PyObject。)

rawrefcount.create_link_pyobj(p, ob)

从现有的 PyObject 结构 ‘ob’ 到新分配的 W_CPyExtPlaceHolderObject ‘p’ 建立链接。您还必须将 REFCNT_FROM_PYPY 添加到 ob->ob_refcnt。对于 PyObject 包含所有数据,而 PyPy 对象只是一个代理的情况。W_CPyExtPlaceHolderObject 应该只有一个包含 PyObject 地址的字段,但这超出了 GC 的范围。

rawrefcount.from_obj(p)

如果使用 create_link_pypy() 从对象 ‘p’ 建立了链接,则返回相应的 ‘ob’。否则,返回 NULL。

rawrefcount.to_obj(Class, ob)

返回 ob->ob_pypy_link,强制转换为 ‘Class’ 的实例。

收集逻辑

纯粹存在于 C 端的对象具有 ob->ob_pypy_link == 0;这些对象纯粹是引用计数的。另一方面,如果 ob->ob_pypy_link != 0,则 ob->ob_refcnt 至少为 REFCNT_FROM_PYPY,并且该对象是“链接”的一部分。

其 ‘p’ 从其他 PyPy 对象无法访问且 ‘ob->ob_refcnt’ 为 REFCNT_FROM_PYPY 或 REFCNT_FROM_PYPY_LIGHT 的链接是那些将要死亡的链接。但它更混乱,因为 PyObject 仍然(通常)需要调用 tp_dealloc,而这不能立即发生(并且可以执行诸如访问该对象指向的其他引用或恢复该对象之类的随机操作)。

令 P = 使用 rawrefcount.create_link_pypy() 创建的链接列表,O = 使用 rawrefcount.create_link_pyobj() 创建的链接列表。列表 O 中的 PyPy 对象都是 W_CPyExtPlaceHolderObject:所有数据都在 PyObjects 中,所有外部引用(如果有)都在 C 中,作为 PyObject * 字段。

因此,在收集过程中,我们对 P 链接执行以下操作

for (p, ob) in P:
    if ob->ob_refcnt != REFCNT_FROM_PYPY
           and ob->ob_refcnt != REFCNT_FROM_PYPY_LIGHT:
        mark 'p' as surviving, as well as all its dependencies

在收集结束时,P 和 O 链接都以这种方式处理

for (p, ob) in P + O:
    if p is not surviving:    # even if 'ob' might be surviving
        unlink p and ob
        if ob->ob_refcnt == REFCNT_FROM_PYPY_LIGHT:
            free(ob)
        elif ob->ob_refcnt > REFCNT_FROM_PYPY_LIGHT:
            ob->ob_refcnt -= REFCNT_FROM_PYPY_LIGHT
        else:
            ob->ob_refcnt -= REFCNT_FROM_PYPY
            if ob->ob_refcnt == 0:
                invoke _Py_Dealloc(ob) later, outside the GC

GC 实现

我们需要年轻或老对象的 P 列表和 O 列表的两个副本。所有四个列表都可以是 'ob' 对象的常规 AddressLists。

我们还需要一个 AddressDict,将 'p' 映射到 P 列表中所有链接的 'ob',并在 PyPy 对象移动时更新它。

进一步说明

XXX XXX 其余部分是理想世界,但作为第一步,我们将 XXX 寻找适应现有 cpyext XXX 所需的最小调整。

对于在 CPython 中不透明的对象,例如 <dict>,我们始终创建一个 PyPy 对象,然后在需要时创建一个空的 PyObject 并使用 create_link_pypy()/REFCNT_FROM_PYPY_LIGHT 将其附加。

对于 <int> 和 <float> 对象,相应的 PyObjects 也包含一个“long”或“double”字段。我们使用 create_link_pypy() 将它们链接起来,并且也可以使用 REFCNT_FROM_PYPY_LIGHT:'tp_dealloc' 不需要调用,而只需调用 free() 即可。

对于 <type> 对象,我们需要 PyPy 和 PyObject 两侧。这些是使用 create_link_pypy()/REFCNT_FROM_PYPY 创建的。

对于从 C 扩展模块分配的自定义 PyXxxObjects,我们需要 create_link_pyobj()。

对于来自 PyPy 的 <str> 或 <unicode> 对象,我们使用 create_link_pypy()/REFCNT_FROM_PYPY_LIGHT,并使用预先分配了字符串大小的 PyObject。如果调用 PyString_AS_STRING(),我们将字符串延迟复制到该区域。

对于 C 扩展模块中的 <str>、<unicode>、<tuple> 或 <list> 对象,我们首先将其分配为仅一个 PyObject,它支持从 C 对数据的修改,就像 CPython 一样。当它被导出到 PyPy 时,我们可以使用 create_link_pyobj() 创建一个 W_CPyExtPlaceHolderObject。

对于来自 PyPy 的 <tuple> 对象,如果它们没有专门化,那么 PyPy 侧会对项目保持常规引用。然后,我们可以分配一个 PyTupleObject 并将借用的 PyObject 指针存储到其中的项目中。这种情况是使用 create_link_pypy()/REFCNT_FROM_PYPY_LIGHT 创建的。如果它专门化了,那么它不起作用,因为项目是在 PyPy 侧即时创建的。在这种情况下,PyTupleObject 需要对 PyObject 项目保持真正的引用,我们使用 create_link_pypy()/ REFCNT_FROM_PYPY。在所有情况下,我们都有一个 PyObjects 的 C 数组,我们可以直接从 PySequence_Fast_ITEMS、PyTuple_ITEMS、PyTuple_GetItem 等返回。

对于来自 PyPy 的 <list> 对象,我们可以使用 cpyext 列表策略。列表将变成一个 PyListObject,就好像它最初是从 C 分配的一样。特殊策略可以(仅)对 PyListObject 保持直接引用,我们可以使用 create_link_pyobj() 或 create_link_pypy()(待定)。然后 PySequence_Fast_ITEMS 也适用于列表,PyList_GetItem 可以返回借用的引用,等等。