Python源码学习笔记 2 整数对象

Python中的整数类型是不可变对象,为了提高python运行效率,内部实现了小整数对象池(数组实现),和通用整数缓冲池(单链表实现)。小整数是可以复用的,而通用整数是即使数值相同也会创建两个不同的整数对象。

1.PyIntObject

该结构仅适用Python2版本,该版本下数字长度大于long型时,对象类型会转变为PyLongObject

PyIntObject结构:

[intobject.h]
typedef struct {
    PyObject_HEAD //标准PyObject头
    long ob_ival; //实际存储数值得变量
} PyIntObject;

整数对象的创建:
提供了三种创建方式,分类为long型创建或从字符串创建,但从字符串创建最终还是需要调用long型创建方式

 PyObject *PyInt_FromLong(long ival) //从long整数创建
 PyObject* PyInt_FromString(char *s, char **pend, int base) //从ASCII字符串创建
#ifdef Py_USING_UNICODE 
  PyObject*PyInt_FromUnicode(Py_UNICODE *s, int length, int base) //从Unicode字符串创建
#endif

整数对象相减:

[intobject.h]
#define PyInt_AS_LONG(op) (((PyIntObject *)(op))->ob_ival)
//宏,牺牲类型安全,换取执行效率

[intobject.c]
#define CONVERT_TO_LONG(obj, lng)       \
    if (PyInt_Check(obj)) {         \
        lng = PyInt_AS_LONG(obj);   \
    }                   \
    else {                  \
        Py_INCREF(Py_NotImplemented);   \
        return Py_NotImplemented;   \
    }
  
static PyObject *
int_sub(PyIntObject *v, PyIntObject *w)
{
    register long a, b, x;
    CONVERT_TO_LONG(v, a);
    CONVERT_TO_LONG(w, b);
    x = a - b;
    if ((x^a) >= 0 || (x^~b) >= 0) //溢出检查
        return PyInt_FromLong(x);//此处印证了Python中整数对象是一个不可变对象
    return PyLong_Type.tp_as_number->nb_subtract((PyObject *)v,
                             (PyObject *)w); //若溢出会将返回一个PyLongObject
}

2.小整数对象

在编程中小整数对象使用频率很高(如循环标记),而从上文中我们已经得知Python中整数对象是不可变对对象,若没有特殊的方法处理,会导致频繁创建小整数对象,严重影响运行效率和浪费内存。
在python内部通过创建一个PyIntObject *的数组small_ints来优化这个情况, 数组默认范围为[-5,257)

[intobject.c]
#ifndef NSMALLPOSINTS
     #define NSMALLPOSINTS       257 //正数
#endif
#ifndef NSMALLNEGINTS
     #define NSMALLNEGINTS       5 //负数
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
     static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
#endif

3.通用整数对象

对于小整数以外的其他整数,python内部使用多个“预先”分配的内存块来缓存这些整数对象,每一个内存块称为一个block_list, 未使用的空间使用free_list穿起来

[intobject.c]

#define BLOCK_SIZE  1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE  8   /* Enough for a 64-bit pointer */
#define N_INTOBJECTS    ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))

struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS]; //PyIntObject数组,用以存储整数
};

typedef struct _intblock PyIntBlock;

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

4.整数对象的创建与删除

整数对象的创建

 [intobject.c]
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);
        return (PyObject *) v;
    }
#endif
    if (free_list == NULL) {//当前block_list无空闲,需要申请新的PyIntBlock
        if ((free_list = fill_free_list()) == NULL) 
            return NULL;
    }
    //使用通用整数对象池
    v = free_list;
    free_list = (PyIntObject *)v->ob_type;
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}

整数对象的删除:
对整数对象进行删除时,实际上是将该对象加入到free_list链表中,以便整型对象的缓冲,避免频繁的malloc操作

static void
int_dealloc(PyIntObject *v)
{
    if (PyInt_CheckExact(v)) {
        /*将该整型对象加入到free_list链表中*/
        Py_TYPE(v) = (struct _typeobject *)free_list;
        free_list = v;
    }
    else
        Py_TYPE(v)->tp_free((PyObject *)v);//若为整型派生对象则仅调用指定的tp_free()
}

小整数对象池的初始化:
在Python虚拟机初始化时_PyInt_Init会被调用,创建小整数缓冲池

int
_PyInt_Init(void)
{
    PyIntObject *v;
    int ival;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    /* 小整数对象仍在block_list中创建,只是将创建后的对象地址存入对应位置small_ints中 */
    for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
        if (!free_list && (free_list = fill_free_list()) == NULL)
            return 0;
        v = free_list;
        free_list = (PyIntObject *)Py_TYPE(v);
        (void)PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        small_ints[ival + NSMALLNEGINTS] = v;//指针存入small_ints
    }
#endif
    return 1;
}

fill_free_list实现:
当free_list为NULL时创建新的整数对象,会触发fill_free_list操作,将malloc一个PyIntBlock,并将内部的object数组中每个整数对象,通过ob_type作为后继(牺牲掉类型安全)形成单链表关系

[object.h]
#define Py_TYPE(ob)             (((PyObject*)(ob))->ob_type)

[intobject.c]
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的next链接到旧block_list上
    block_list = (PyIntBlock *)p; //另新block为block_list头结点
    p = &((PyIntBlock *)p)->objects[0];
    q = p + N_INTOBJECTS;
    while (--q > p)
        Py_TYPE(q) = (struct _typeobject *)(q-1); //创建block内部空闲整数对象间连接关系
    Py_TYPE(q) = NULL;
    return p + N_INTOBJECTS - 1;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容