开发,设计,生活

编程语言

  • 2014-07-21 05:36:02 2014-08-19 09:56:41[U] 编程语言

    python 中的内置对象

    python 中常用的内置对象有:整数对象,字符串对象,列表对象,字典对象。这些对象在python中使用最多,所以在实现上提供缓存机制,以提高运行效率。


    整数对象 (PyIntObject)

    python 中的整数对象是不可变对象(immutable),即创建了一个 python 整数对象之后,不能再改变该对象的值。整数对象有小整数缓存池,用来提高效率。

    python 为创建整数对象提供了下面三种方法,其中 PyInt_FromString 和 PyInt_FromUnicode 内部也是调用 PyInt_FromLong 创建的整数对象。

    PyObject * PyInt_FromLong(long ival);
    PyObject * PyInt_FromString(char *s, char **pend, int base);
    PyObject * PyInt_FromUnicode(Py_UNICODE *s, Py_ssize_t length, int base);
    

    下面主要看下 PyInt_FromLong内部的实现

    PyObject *
    PyInt_FromLong(long ival)
    {
        register PyIntObject *v;
    #if NSMALLNEGINTS + NSMALLPOSINTS > 0
        // NSMALLNEGINTS = 5,NSMALLPOSINTS = 257 
        // 如果创建的整数对象值在[-5,256] 则从 small_ints 缓存池中直接返回整数对象
        if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
            v = small_ints[ival + NSMALLNEGINTS];
            Py_INCREF(v);
    #ifdef COUNT_ALLOCS
            if (ival >= 0)
                quick_int_allocs++;
            else
                quick_neg_int_allocs++;
    #endif
            return (PyObject *) v;
        }
    #endif
        // 创建 free_list 缓存列表,提供创建不在 small_ints 缓存池内的对象
        if (free_list == NULL) {
            if ((free_list = fill_free_list()) == NULL)
                return NULL;
        }
        // 从 free_list 中获取新对象
        v = free_list;
        free_list = (PyIntObject *)Py_TYPE(v);
    
        // 初始化并返回新的整数对象
        PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        return (PyObject *) v;
    }
    

    整数对象销毁的操作,仅仅记录释放的内存到 free_list 中

    static void
     int_dealloc(PyIntObject *v)
     {
         if (PyInt_CheckExact(v)) {
             Py_TYPE(v) = (struct _typeobject *)free_list;
             free_list = v;
         }
         else
             Py_TYPE(v)->tp_free((PyObject *)v);
     }
    

    free_list 与 PyIntBlock 一起管理小整数范围以外的整数对象缓存

    struct _intblock {
        struct _intblock *next;
        PyIntObject objects[N_INTOBJECTS];
    };
    
    typedef struct _intblock PyIntBlock;
    
    static PyIntBlock *block_list = NULL;
    static PyIntObject *free_list = NULL;
    


    整数对象的实现机制总结

    1. small_ints 是小整数对象的缓存池,范围是 [-5,256],可以快速的提供缓存中的对象,仅仅增加对象的引用计数就可以。
    2. 非小整数对象即使值相同,也会创建2个不同的整数对象。
    3. free_list 和 block_list 保存创建过的整数对象分配的内存,在创建新的整数对象时,直接从free_list获取对象的内存空间,初始化对象后就可以使用。
    4. python 的整数对象在释放的时候,整数对象占用的内存将继续保存在 block_list 中,并且在 free_list 中记录,将来提供给新创建的整数对象使用。(就是创建整数后分配的内存不会归还给操作系统,所以尽量降低同一时刻分配的整数数量,这样可以降低内存消耗)


    字符串对象

    python 的字符串对象是变长对象,同时也是不可变对象,字符串不可以修改。

    python 内部创建字符串对象的两种方法,PyString_FromStringAndSize 指定了长度。

    PyObject * PyString_FromString(const char *str);
    PyObject * PyString_FromStringAndSize(const char *str, Py_ssize_t size);
    

    看下 PyString_FromString 内部的实现

    PyObject *
    PyString_FromString(const char *str)
    {
        register size_t size;
        register PyStringObject *op;
    
        assert(str != NULL);
        size = strlen(str);
        if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) {
            PyErr_SetString(PyExc_OverflowError,
                "string is too long for a Python string");
            return NULL;
        }
        // 判断,单字符可以从缓冲中直接返回
        if (size == 0 && (op = nullstring) != NULL) {
    #ifdef COUNT_ALLOCS
            null_strings++;
    #endif
            Py_INCREF(op);
            return (PyObject *)op;
        }
        if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {
    #ifdef COUNT_ALLOCS
            one_strings++;
    #endif
            Py_INCREF(op);
            return (PyObject *)op;
        }
    
        /* Inline PyObject_NewVar */
        op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size);
        if (op == NULL)
            return PyErr_NoMemory();
        PyObject_INIT_VAR(op, &PyString_Type, size);
        op->ob_shash = -1;
        op->ob_sstate = SSTATE_NOT_INTERNED;
        Py_MEMCPY(op->ob_sval, str, size+1);
        /* 创建单字符的缓冲 */
        if (size == 0) {
            PyObject *t = (PyObject *)op;
            PyString_InternInPlace(&t);
            op = (PyStringObject *)t;
            nullstring = op;
            Py_INCREF(op);
        } else if (size == 1) {
            PyObject *t = (PyObject *)op;
            PyString_InternInPlace(&t);
            op = (PyStringObject *)t;
            characters[*str & UCHAR_MAX] = op;
            Py_INCREF(op);
        }
        return (PyObject *) op;
    }
    

    字符缓冲池,这个缓冲池会在第一次创建单字符对象的时候填充,如上面 PyString_FromString 函数内。

    #define UCHAR_MAX 0xff
    static PyStringObject *characters[UCHAR_MAX + 1];
    

    性能相关的 '+' 操作和 join 操作。每次 '+' 操作都需要新创建对象,性能较差。join 先计算结果对象的总长度,创建一个结果字符串对象,然后拷贝数据到结果内存位置,所以性能较好。

    static PyObject *
    string_concat(register PyStringObject *a, register PyObject *bb)
    {
        // ...
        op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size);
        if (op == NULL)
            return PyErr_NoMemory();
        PyObject_INIT_VAR(op, &PyString_Type, size);
        op->ob_shash = -1;
        op->ob_sstate = SSTATE_NOT_INTERNED;
        Py_MEMCPY(op->ob_sval, a->ob_sval, Py_SIZE(a));
        Py_MEMCPY(op->ob_sval + Py_SIZE(a), b->ob_sval, Py_SIZE(b));
        op->ob_sval[size] = '\0';
        return (PyObject *) op;
    }
    
    static PyObject *
    string_join(PyStringObject *self, PyObject *orig)
    {
        // ...
        // 计算拼接后字符串总长度
        for (i = 0; i < seqlen; i++) {
            const size_t old_sz = sz;
            item = PySequence_Fast_GET_ITEM(seq, i);
            if (!PyString_Check(item)){
    #ifdef Py_USING_UNICODE
                if (PyUnicode_Check(item)) {
                    /* Defer to Unicode join.
                     * CAUTION:  There's no gurantee that the
                     * original sequence can be iterated over
                     * again, so we must pass seq here.
                     */
                    PyObject *result;
                    result = PyUnicode_Join((PyObject *)self, seq);
                    Py_DECREF(seq);
                    return result;
                }
    #endif
                PyErr_Format(PyExc_TypeError,
                             "sequence item %zd: expected string,"
                             " %.80s found",
                             i, Py_TYPE(item)->tp_name);
                Py_DECREF(seq);
                return NULL;
            }
            sz += PyString_GET_SIZE(item);
            if (i != 0)
                sz += seplen;
            if (sz < old_sz || sz > PY_SSIZE_T_MAX) {
                PyErr_SetString(PyExc_OverflowError,
                    "join() result is too long for a Python string");
                Py_DECREF(seq);
                return NULL;
            }
        }
    
        // 为拼接后字符串分配空间
        res = PyString_FromStringAndSize((char*)NULL, sz);
        if (res == NULL) {
            Py_DECREF(seq);
            return NULL;
        }
    
        // 拷贝拼接字符串到新创建的字符串的内存位置
        p = PyString_AS_STRING(res);
        for (i = 0; i < seqlen; ++i) {
            size_t n;
            item = PySequence_Fast_GET_ITEM(seq, i);
            n = PyString_GET_SIZE(item);
            Py_MEMCPY(p, PyString_AS_STRING(item), n);
            p += n;
            if (i < seqlen - 1) {
                Py_MEMCPY(p, sep, seplen);
                p += seplen;
            }
        }
    
        Py_DECREF(seq);
        return res;
    }
    

    字符串对象的实现机制总结

    1. 字符串对象实现了单字符的缓冲区,创建单字符的对象时直接从缓冲区中获取对象。
    2. 多字符串对象的拼接使用 join 性能好于 '+'。