为什么命名元组总是由python的GC跟踪?

时间:2013-11-04 15:00:04

标签: python garbage-collection

正如我们(或至少我)在this answer中学习的那些仅包含不可变值的元组不会被python的垃圾收集器跟踪,一旦它发现它们永远不会参与引用循环:

>>> import gc
>>> x = (1, 2)
>>> gc.is_tracked(x)
True
>>> gc.collect()
0
>>> gc.is_tracked(x)
False

为什么namedtuple s不是这种情况,它是来自具有命名字段的collections模块的元组的子类?

>>> import gc
>>> from collections import namedtuple
>>> foo = namedtuple('foo', ['x', 'y'])
>>> x = foo(1, 2)
>>> gc.is_tracked(x)
True
>>> gc.collect()
0
>>> gc.is_tracked(x)
True

他们的实施中是否有固有的东西可以阻止这种情况,还是只是被忽视了?

2 个答案:

答案 0 :(得分:7)

我能找到的关于此的唯一评论是在Python源代码的gcmodule.c文件中:

  

注意:有关未跟踪可变对象的信息。          某些类型的容器不能参与引用循环,因此不需要被垃圾收集器跟踪。   取消跟踪这些对象可以降低垃圾收集的成本。   但是,确定哪些对象可能未被跟踪不是免费的,   并且必须权衡成本与垃圾的利益   集合。

     

何时解开容器有两种可能的策略:

     
      
  1. 创建容器时。
  2.   
  3. 当垃圾收集器检查容器时。
  4.         

    仅包含不可变对象的元组(整数,字符串等,   并且递归地,不可变对象的元组)不需要   跟踪。解释器创建了大量的元组,其中许多都是   直到垃圾收集才能生存。因此   不值得在创建时取消符合条件的元组。

         

    相反,除了空元组之外的所有元组都被跟踪   创建。在垃圾收集期间,确定是否有   幸存的元组可以不被攻击。如果全部,元组可以不被跟踪   其内容已被跟踪。检查元组   在所有垃圾收集周期中未跟踪。它可能需要更多   一个周期来取消一个元组。

         

    仅包含不可变对象的字典也不需要   跟踪。字典在创建时未跟踪。如果跟踪   item被插入到字典中(作为键或值),   字典被跟踪。在完整的垃圾收集期间(全部   几代人,收藏家将不会删除任何词典   内容未被跟踪。

         

    该模块提供了python函数is_tracked(obj),其中   返回对象的当前跟踪状态。随后   垃圾收集可能会更改对象的跟踪状态。   问题#4688中引入了某些容器​​的未跟踪,并且针对问题#14775对算法进行了改进。

(请参阅链接的问题以查看引入的实际代码以允许取消跟踪)

这个评论有点含糊不清,但表示选择哪个“未跟踪”对象的算法适用于通用容器。这意味着代码只检查tuple s(和dict s),而不是它们的子类。

您可以在文件的代码中看到:

/* Try to untrack all currently tracked dictionaries */
static void
untrack_dicts(PyGC_Head *head)
{
    PyGC_Head *next, *gc = head->gc.gc_next;
    while (gc != head) {
        PyObject *op = FROM_GC(gc);
        next = gc->gc.gc_next;
        if (PyDict_CheckExact(op))
            _PyDict_MaybeUntrack(op);
        gc = next;
    }
}

请注意对PyDict_CheckExact的调用,并且:

static void
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
{
    PyGC_Head *gc = young->gc.gc_next;

  /* omissis */
            if (PyTuple_CheckExact(op)) {
                _PyTuple_MaybeUntrack(op);
            }

请注意拨打PyTuple_CheckExact

另请注意,tuple的子类不必是不可变的。这意味着如果您想在tupledict之外扩展此机制,则需要通用的is_immutable函数。如果可能由于Python的动态(例如,类的方法可能在运行时发生变化,而tuple无法实现这一点,那么这将是非常昂贵的非常昂贵,因为它是内置的类型)。因此,开发者选择坚持一些特殊情况只有一些着名的内置插件。


这就是说,我相信他们可以特殊情况namedtuple,因为它们是非常简单的类。例如,当您调用namedtuple创建 new 类时会出现一些问题,因此GC应检查子类。 这可能是代码的问题,如:

class MyTuple(namedtuple('A', 'a b')):
    # whatever code you want
    pass

因为MyTuple类需要是不可变的,所以GC应该检查该类是否是namedtuple直接子类安全。但是我很确定这种情况有变通方法。

他们可能没有,因为namedtuple是标准库的一部分,而不是python核心。也许开发人员不想让核心依赖于标准库的模块。

所以,回答你的问题:

  • 不,他们的实施中没有任何内容可以防止namedtuple
  • 的无法跟踪
  • 不,我相信他们“只是忽略了”这个。但是,只有python开发人员才能明确回答他们选择不包含它们的原因。我的猜测是,他们认为这不会为改变提供足够的好处,他们不想让核心依赖于标准库。

答案 1 :(得分:3)

@Bakunu给出了一个很好的答案 - 接受它: - )

这里的光泽:没有未跟踪的噱头是“免费的”:在运行时和维护棘手代码的爆炸中都有实际成本。基本元组和字典类型非常大量使用,无论是用户程序还是CPython实现,并且经常可以解决它们。所以特殊套管它们值得一些痛苦,并且有利于“几乎所有”程序。虽然可以找到可以从未跟踪namedtuple(或......)中获益的程序示例,但它不会使CPython实现或大多数用户程序受益。但它会对所有程序强加成本(gc代码中的更多条件要求“这是namedtuple”等等。

请注意,所有容器对象都受益于CPython的“世代”循环gc噱头:给定容器存活的集合越多,容器扫描的频率就越低(因为容器被移动到“老一代”,扫描较少)经常)。因此,除非容器类型大量出现(通常是元组,对于dicts很少是真的)或者容器包含大量对象(通常对于dicts,对于元组很少是真的),因此潜在的增益很少