Bootstrap

Python源码剖析(三)整数对象 2

2. 整数的创建与运行

创建对象的方法:

  1. 通过Python C API来创建

  2. 通过设计的类型对象创建实例对象

    内建对象也是采用这种方法(tp_new\tp_init),但是最终还是会调用为特定内建对象准备的C API

那么是不是所有的整型(整数)对象都是采用这种方式呢?

  • 类型角度:从 C long int 生成(from String、from float、from Unicode)PyObject?

    /* Create a new int object from a C long int */
    
    PyObject *
    PyLong_FromLong(long ival)
    {
    // ....
    }
    
  • 整数大小角度:所有区间的整数一样吗?

必须深入理解整数对象在内存中的组织方式(整数对象系统的结构)

2.1 小整数对象池(Python2版本)

https://docs.python.org/3.9/c-api/memory.html

对象的引用或地址存储在栈区

该对象的真实数据存储在堆区

出现背景:频繁地申请和释放内存,降低运行效率,造成大量的内存水平

定义:Python直接将小整数[-5, 256]对应的PyLongObject缓存在内存中,并将其指针存放在small_ints中。

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array so that they
   can be shared.
   The integers that are saved are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
#endif

小整数对象池的初始化

// Objects/longobject.c
int
_PyInt_Init(void)
{
    PyIntObject *v;
    int ival;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
        if (!free_list && (free_list = fill_free_list()) == NULL)
            return 0;
        /* PyObject_New is inlined */
        v = free_list;
        free_list = (PyIntObject *)Py_TYPE(v);
        (void)PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        small_ints[ival + NSMALLNEGINTS] = v;
    }
#endif
    return 1;
}

在这里插入图片描述

// intobject.c
struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];  // 用于存储被缓存的PyIntObject对象(内存的一块)
};
typedef struct _intblock PyIntBlock;

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

对于大整数呢?时间与空间的选择

提供一块内存空间供大整数轮流使用,”谁需要谁申请“,不同的整数可能是同一块内存。

2.2 大整数(通用整数)对象池(Python2版本)

Motivation:空闲状态的内存必须被按一定的规则组织起来,这样创建新的对象时才能快速获得所需的内存。

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

在这里插入图片描述

2.2.1 对象的创建

PyObject *
PyInt_FromLong(long ival)
{
    register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    // 尝试使用小整数对象池
    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
    // 为通用整数对象池申请新的内存空间
    if (free_list == NULL) {
        if ((free_list = fill_free_list()) == NULL)
            return NULL;
    }
    /* Inline PyObject_New */
    // 内联PyObject_New的行为
    v = free_list;
    free_list = (PyIntObject *)Py_TYPE(v);
    (void)PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}

调用 fill_free_list 创建新的block的两种情况:

  • 首次调用PyInt_FromLong
  • 所有block的空闲的内存都被使用完了

在这里插入图片描述

将数组转换成单链表,依靠PyObject的ob_type指针(放弃了对安全类型的坚持),从后往前开始连接。

static PyIntObject *
fill_free_list(void)
{
    PyIntObject *p, *q;
    /* Python's object allocator isn't appropriate for large blocks. */
    p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
    if (p == NULL)
        return (PyIntObject *) PyErr_NoMemory();
    ((PyIntBlock *)p)->next = block_list;
    block_list = (PyIntBlock *)p;
    /* Link the int objects together, from rear to front, then return
       the address of the last int object in the block. */
    p = &((PyIntBlock *)p)->objects[0];
    q = p + N_INTOBJECTS;
    while (--q > p)
        Py_TYPE(q) = (struct _typeobject *)(q-1);
    Py_TYPE(q) = NULL;
    return p + N_INTOBJECTS - 1;
}

在这里插入图片描述

2.2.2 对象的删除与维护

对象销毁后内存空闲不用相当于内存泄露(解决措施:头插法)

内存不会释放,不会归还给系统,供其他大整数轮流使用

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);
}

在这里插入图片描述

所以,大整数对象池使用内存的大小与同一时刻共存的大整数对象个数的最大值有关,与历史上创建的整数对象个数无关。

2.3 小整数对象池(Python3版本)

从 PyLong_Type 可以看出,创建一个整数对象的入口函数为 long_new

// Objects/clinic/longobject.c.h
static PyObject *
long_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
  // ...
}

// Objects/longobject.c
static PyObject *
long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase)
/*[clinic end generated code: output=e47cfe777ab0f24c input=81c98f418af9eb6f]*/
{
  // ...
}
static PyObject *
get_small_int(sdigit ival)
{
    PyObject *v;
    assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
    v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
    Py_INCREF(v);
#ifdef COUNT_ALLOCS
    if (ival >= 0)
        quick_int_allocs++;
    else
        quick_neg_int_allocs++;
#endif
    return v;
}
#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } 


// Objects/longobject.c

int
_PyLong_Init(void)
{
    // ......
}

2.4 大整数(通用整数)对象池(Python3版本)

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;
    }
    return (PyLongObject*)PyObject_INIT_VAR(result, &PyLong_Type, size);
}
// 分别 Python2 与 Python3 中执行下列代码
// Intern mechanism
a = 345
b = a
print(a is b)

c = 456
d = 456
print(c is d)
;