08月12, 2020

5. 创建 Long Object

A glance

  • 针对小整数的优化;
  • 小整数区间;
  • ob_type 和全局整数类型对象 PyLong_Type

Start

整数对象是 Python 中最常用的对象,如果每用到一个整数对象就要进行一次对象的创建、销毁,那成本就太高了,Python 在底层对不同的整数区别对待,以提高效率。

Small int

对于一些足够小的整数,Python 会预先将他们创建好,并且常驻在解释器内存的 small_ints[] 数组中,这样小整数就不需要频繁创建对象,直接引用这些全局对象就可以了。

在 Objects/longobject.c 中定义了 LongObject 的主要函数,其中 get_small_int 用于从全局解释器中获取这些小整数对象。

 static PyObject *
get_small_int(sdigit ival)
{
    assert(IS_SMALL_INT(ival));
    PyInterpreterState *interp = _PyInterpreterState_GET();
    PyObject *v = (PyObject*)interp->small_ints[ival + NSMALLNEGINTS];
    Py_INCREF(v);
    return v;
}

首先通过 _PyInterpreterState_GET 获取解释器,然后访问其中的小整数数组。哪些数字属于小整数呢?

#define IS_SMALL_INT(ival) (-NSMALLNEGINTS <= (ival) && (ival) < NSMALLPOSINTS)

由这个宏可知,当一个整数在 -NSMALLNEGINTS 和 NSMALLPOSINTS 之间时,就是小整数,它们最终的定义在 Include/internal/pycore_interp.h 中:

#define _PY_NSMALLPOSINTS           257
#define _PY_NSMALLNEGINTS           5

所以在 -5 ~ 257 之间的整数会按照小整数处理,直接引用全局对象,而非创建新对象。

创建 LongObject

在 Object/longobject.c 中实现了多种源数据创建 LongObject,最终调用的是下面的函数:

PyLongObject *
_PyLong_New(Py_ssize_t size)
{
    PyLongObject *result;
    /* Number of bytes needed is: offsetof(PyLongObject, ob_digit) +
       sizeof(digit)*size.  Previous incarnations of this code used
       sizeof(PyVarObject) instead of the offsetof, but this risks being
       incorrect in the presence of padding between the PyVarObject header
       and the digits. */
    if (size > (Py_ssize_t)MAX_LONG_DIGITS) {
        PyErr_SetString(PyExc_OverflowError,
                        "too many digits in integer");
        return NULL;
    }
    result = PyObject_MALLOC(offsetof(PyLongObject, ob_digit) +
                             size*sizeof(digit));
    if (!result) {
        PyErr_NoMemory();
        return NULL;
    }
    _PyObject_InitVar((PyVarObject*)result, &PyLong_Type, size);
    return result;
}

函数非常简单,计算内存大小,申请内存,最后进行初始化。其中最重要的一步是 _PyObject_InitVar((PyVarObject)result, &PyLong_Type, size);*

static inline void
_PyObject_InitVar(PyVarObject *op, PyTypeObject *typeobj, Py_ssize_t size)
{
    assert(op != NULL);
    Py_SET_SIZE(op, size);
    _PyObject_Init((PyObject *)op, typeobj);
}

在 _PyObject_Init 中,将当前对象与它的 type 对象关联起来,对于 LongObject 它的 ob_type 会指向 PyLong_Type,其定义在 Object/longobject.c 的第 5632 行:

PyTypeObject PyLong_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "int",                                      /* tp_name */
    offsetof(PyLongObject, ob_digit),           /* tp_basicsize */
    sizeof(digit),                              /* tp_itemsize */
    /*
    ************* 忽略了一些代码 ************
    */
    0,                                          /* tp_alloc */
    long_new,                                   /* tp_new */
    PyObject_Del,                               /* tp_free */
};

这是一个全局对象,可以看到第二项为 “int”,即 tp_name 类型名称,看到这里我们终于确定了一个实施 LongObject 就是 Int 类型,这里可以修改 “int” 为 “Integer”,重新编译可以看到整数的类型名称从 “int” 变为 “Integer”。

title

回顾上一节中 LongObject 的内存图,上面的代码清晰的说明了 ob_type 的指向,如果注意观察 PyLong_Type 的定义,PyLong_Type 自身也是一个 PyVarObject,PyLong_Type 不仅表示一个类型,它自己也有类型,其定义的第一行 PyVarObject_HEAD_INIT(&PyType_Type, 0) 将它的 ob_type 指向另一个类型对象 PyType_Type,说明 PyLong_Type 是一个 “表示类型对象” 的类型对象,有点绕了!如果继续追究下去,会发现 PyType_Type 也有类型,它的 ob_type 指向自己,它自己也是一个 “表示类型对象” 的类型对象。下面补全 LongObject 的图示:

title

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

-- EOF --