zg手冊 之 python2.7.7源碼分析(2)-- python 的整數對象和字符串對象

python 中的內置對象

python 中經常使用的內置對象有:整數對象,字符串對象,列表對象,字典對象。這些對象在python中使用最多,因此在實現上提供緩存機制,以提升運行效率。python


整數對象 (PyIntObject)

python 中的整數對象是不可變對象(immutable),即建立了一個 python 整數對象以後,不能再改變該對象的值。緩存

python 爲建立整數對象提供了下面三種方法,其中 PyInt_FromString 和 PyInt_FromUnicode 內部也是調用 PyInt_FromLong 建立的整數對象。python2.7

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],能夠快速的提供緩存中的對象,僅僅增長對象的引用計數就能夠。ui

  2. free_list 和 block_list 保存建立過的整數對象分配的內存,在建立新的整數對象時,直接從free_list獲取對象的內存空間,初始化對象後就可使用。spa

  3. python 的整數對象在釋放的時候,整數對象佔用的內存將繼續保存在 block_list 中,而且在 free_list 中記錄,未來提供給新建立的整數對象使用。(就是建立整數後分配的內存不會歸還給操做系統,因此儘可能下降同一時刻分配的整數數量,這樣能夠下降內存消耗)操作系統


字符串對象

python 的字符串對象是變長對象,同時也是不可變對象,字符串不能夠修改。code

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 性能好於 '+'。


原文連接: zg手冊 之 python2.7.7源碼分析(2)-- python 的整數對象和字符串對象

相關文章
相關標籤/搜索