9506191f1c54ac14131f7ab5b2376664
Python的字符串对象优化

上周我们探讨了Python字符串对象的设计,今天我们继续这个话题,来研究下Python中是如何对字符串对象进行优化的。

Python的Intern机制

在上文我们阐述PyStringObject创建过程的时候,我们删除了部分优化的代码,这部分的代码其实就是intern机制相关,让我们着重来看一下:

if (size == 0) {
    PyObject *t = (PyObject *)op;
    PyString_InternInPlace(&t);
    op = (PyStringObject *)t;
    nullstring = op;
    Py_INCREF(op);
} else if (size == 1 && str != NULL) {
    PyObject *t = (PyObject *)op;
    PyString_InternInPlace(&t);
    op = (PyStringObject *)t;
    characters[*str & UCHAR_MAX] = op;
    Py_INCREF(op);
}

从代码中不难看出,在创建长度为0或者长度为1的字符串的时候,我们会执行PyString_InternInPlace,这个函数具体实现如下:

void
PyString_InternInPlace(PyObject **p)
{
    register PyStringObject *s = (PyStringObject *)(*p);
    PyObject *t;

    if (PyString_CHECK_INTERNED(s))
    return;

    if (interned == NULL) {
        interned = PyDict_New();
        if (interned == NULL) {
            PyErr_Clear(); /* Don't leave an exception */
            return;
        }
    }
    t = PyDict_GetItem(interned, (PyObject *)s);
    if (t) {
        Py_INCREF(t);
        Py_SETREF(*p, t);
        return;
    }

    if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) {
        PyErr_Clear();
        return;
    }
    /* The two references in interned are not counted by refcnt.
       The string deallocator will take care of this */
    Py_REFCNT(s) -= 2;
    PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;
}
  • interned是一个字典的数据结构
  • 如果一个字符串已经被确定进行了intern化操作,就直接返回了。
  • 我们对创建的字符串对象在字典中查询是不是已经存在了,存在了就进行了复用,修改对应的引用计数就好。
  • 没有的话就将这个对象存入字典,key和value都是这个对象本身。

当然,对于被intern了的PyStringObject,在析构的时候也需要特别的处理一次,具体逻辑在string_dealloc中,如下所示:

static void
string_dealloc(PyObject *op)
{
    switch (PyString_CHECK_INTERNED(op)) {
        case SSTATE_NOT_INTERNED:
            break;

        case SSTATE_INTERNED_MORTAL:
            /* revive dead object temporarily for DelItem */
            Py_REFCNT(op) = 3;
            if (PyDict_DelItem(interned, op) != 0)
                Py_FatalError(
                    "deletion of interned string failed");
            break;

        case SSTATE_INTERNED_IMMORTAL:
            Py_FatalError("Immortal interned string died.");

        default:
            Py_FatalError("Inconsistent interned string state.");
    }
    Py_TYPE(op)->tp_free(op);
}

其中,SSTATE_INTERNED_MORTAL相关的代码逻辑就是从interned字典中移除字符串的地方。

虽然逻辑看起来很清晰易懂,但是结合创建过程,我们发现许多地方对引用计数的修改都令人费解,
比如为什么在PyString_InternInPlace

/* The two references in interned are not counted by refcnt.
       The string deallocator will take care of this */
    Py_REFCNT(s) -= 2;

又比如为什么string_dealloc中要Py_REFCNT(op) = 3;

top Created with Sketch.