08月12, 2020

11. 创建 Unicode 对象-2

上一节已经大体了解 Unicode 对象的两种创建方式,一种是 compact 模式,即 data 紧跟在类型结构之后,另一种 data 是一个独立的内存,通过指针与类型结构关联在一起。Python 对 String 的处理中,大量使用了缓存技术,以降低内存消耗,在整个解释器的生命周期中存在三种常驻对象。

unicode_empty 对象

该对象定义在 Objects/unicodeobject.c:209 行。

/* The empty Unicode object is shared to improve performance. */
static PyObject *unicode_empty = NULL;

#define _Py_INCREF_UNICODE_EMPTY()                      \
    do {                                                \
        if (unicode_empty != NULL)                      \
            Py_INCREF(unicode_empty);                   \
        else {                                          \
            unicode_empty = PyUnicode_New(0, 0);        \
            if (unicode_empty != NULL) {                \
                Py_INCREF(unicode_empty);               \
                assert(_PyUnicode_CheckConsistency(unicode_empty, 1)); \
            }                                           \
        }                                               \
    } while (0)

这是一个全局对象,第一次调用 _Py_INCREF_UNICODE_EMPTY() 宏的时候,会创建这个对象,之后只会不断的增减 unicode_empty 的引用计数,所有的空 String 对象都指向这个实例。

unicode_latin1 数组

对于那些编码小于 256 的单个字符,则会全部缓存在一个全局数组中。

/* Single character Unicode strings in the Latin-1 range are being
   shared as well. */
static PyObject *unicode_latin1[256] = {NULL};

可以通过下面的函数获得这全局拉丁字母表中的对象:

// Objects/unicodeobject.c:2032
static PyObject*
get_latin1_char(unsigned char ch)
{
    PyObject *unicode = unicode_latin1[ch];
    if (!unicode) {
        unicode = PyUnicode_New(1, ch);
        if (!unicode)
            return NULL;
        PyUnicode_1BYTE_DATA(unicode)[0] = ch;
        assert(_PyUnicode_CheckConsistency(unicode, 1));
        unicode_latin1[ch] = unicode;
    }
    Py_INCREF(unicode);
    return unicode;
}

代码非常简单,需要注意的是 PyUnicode_New 会获得索引计数为 1 的对象实例,之后的 Py_INCREF(unicode); 则会再增加一次引用计数,这意味着这些字符的引用计数永远不会为 0,真的就可以永久存在了。

interned 机制

回顾 UnicodeObject.state.interned,这个标志位的作用即表示当前对象是否已经驻留内存。所有字符串对象都在全局一个全局字典 interned 中保存,其保存方式是 key、value 均为字符串本身,intern 通过下面函数实现,已经删除无关代码:

void
PyUnicode_InternInPlace(PyObject **p)
{
    PyObject *s = *p;
    PyObject *t;

    if (s == NULL || !PyUnicode_Check(s))
        return;

    // 初始化 interned 字典
    if (interned == NULL) {
        interned = PyDict_New();
        if (interned == NULL) {
            PyErr_Clear(); /* Don't leave an exception */
            return;
        }
    }
    // 尝试缓存,t 是命中 value
    t = PyDict_SetDefault(interned, s, s);

    // 如果是新值,那么就再增加一次 t 的索引计数
    if (t != s) {
        Py_INCREF(t);
        Py_SETREF(*p, t);
        return;
    }

    // 如果命中缓存,减去设置 interned 字典时多增加的两个引用计数
    Py_REFCNT(s) -= 2;
    _PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL;
}

Python 通过这三个全局对象减少重复对象的内存消耗,思路与之前处理整数对象时的小整数池类似,通过提供全局共享对象提高内存利用率。

本文链接:http://www.thinkinpython.com/post/deep_ptyhon_vm_11.html

-- EOF --