Python源码剖析笔记2-Python整数对象

Python源码剖析笔记2-Python整数对象

千里之行始于足下,从简单的类别开始分析,由浅入深也不至于自己丧失信心。先来看看Python整数对象,也就是python中的PyIntObject对象,对应的类型对象是PyInt_Type。

1 Python整数对象概览

为了性能考虑,python中对小整数有专门的缓存池,这样就不需要每次使用小整数对象时去用malloc分配内存以及free释放内存。python2.5.6中默认小整数的范围为[-5, 257),你也可以修改这个范围并重新编译Python。

小整数有缓存池,那么小整数之外的大整数怎么避免重复分配和回收内存呢?Python的方案是PyIntBlock。PyIntBlock这个结构就是一块内存,里面保存PyIntObject对象。一个PyIntBlock默认存放N_INTOBJECTS对象,2.5.6版本在32位ubuntu中这个值为82.PyIntBlock链表通过block_list维护,每个block中都维护一个PyIntObject数组objects,block的objects可能会有些内存空闲,因此需要另外用一个free_list链表串起来这些空闲的项以方便再次使用。objects数组中的PyIntObject对象通过ob_type字段从后往前链接。

小整数的缓存池最终实现也是生存在block_list维护的内存上,在python初始化时,会调用_PyInt_Init函数申请内存并创建小整数对象。

下面是一个简单的数据结构图,表示了这种关系。


integer.png

2 PyIntObject和PyInt_Type

下面看看PyIntObject和PyInt_Type的定义(在源文件Includes/intobject.h和Objects/intobject.c)。

//先补上PyObject_HEAD

#define PyObject_HEAD                   \
        Py_ssize_t ob_refcnt;           \
        struct _typeobject *ob_type;

#define PyObject_HEAD_INIT(type)        \
        1, type,

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

从定义中可以看到,在32位系统中,一个PyIntObject结构体所占大小为12个字节,这个也可以通过python自带的模块来验证,运行 import sys; sys.getsizeof(3)

PyInt_Type是PyType_Object类型的对象,它的定义在Objects/intobject.c中。

//PyTypeObject定义在Includes/object.h中

PyTypeObject PyInt_Type = {
    PyObject_HEAD_INIT(&PyType_Type) //初始化对象头部
    0,                               //因为PyTypeObject是个PyVarObject对象,因此这里需要设置下大小为0.
    "int",                           //用来打印的字段,比如我们type(3)返回的int字符串就是来自这里。
    sizeof(PyIntObject),             //对象基本大小
    0,                               //如果是可变大小对象,这个字段是对象里面存储项的大小
    (destructor)int_dealloc,        /* tp_dealloc */
    (printfunc)int_print,           /* tp_print */
    0,                  /* tp_getattr */
    0,                  /* tp_setattr */
    (cmpfunc)int_compare,           /* tp_compare */
    (reprfunc)int_repr,         /* tp_repr */
    &int_as_number,             /* tp_as_number */
    0,                  /* tp_as_sequence */
    0,                  /* tp_as_mapping */
    (hashfunc)int_hash,         /* tp_hash */
        0,                  /* tp_call */
        (reprfunc)int_repr,         /* tp_str */
    PyObject_GenericGetAttr,        /* tp_getattro */
    0,                  /* tp_setattro */
    0,                  /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
        Py_TPFLAGS_BASETYPE,        /* tp_flags */
    int_doc,                /* tp_doc */
    0,                  /* tp_traverse */
    0,                  /* tp_clear */
    0,                  /* tp_richcompare */
    0,                  /* tp_weaklistoffset */
    0,                  /* tp_iter */
    0,                  /* tp_iternext */
    int_methods,                /* tp_methods */
    0,                  /* tp_members */
    0,                  /* tp_getset */
    0,                  /* tp_base */
    0,                  /* tp_dict */
    0,                  /* tp_descr_get */
    0,                  /* tp_descr_set */
    0,                  /* tp_dictoffset */                                                                                                                    
    0,                  /* tp_init */
    0,                  /* tp_alloc */
    int_new,                /* tp_new */
    (freefunc)int_free,                 /* tp_free */
};

//Objects/intobject.c中定义了PyIntObject支持的数值操作,一共39个。
static PyNumberMethods int_as_number = {
    (binaryfunc)int_add,    /*nb_add*/
    (binaryfunc)int_sub,    /*nb_subtract*/
    (binaryfunc)int_mul,    /*nb_multiply*/
    (binaryfunc)int_classic_div, /*nb_divide*/
    (binaryfunc)int_mod,    /*nb_remainder*/
    (binaryfunc)int_divmod, /*nb_divmod*/
    ...
};

看了PyInt_Type定义,发现主要就是对象头部初始化以及对一些函数的初始化设置。如int_compare是PyIntObject对象的比较函数,而int_print是打印PyIntObject的函数等。此外,还有个很重要的初始化的地方是int_as_number,这个结构定义了一个对象作为数值对象时的操作信息。如int_add,int_sub等用来执行整数加减。

3 PyIntObject创建和相关函数

python中为PyIntObject对象创建主要提供了3个函数,如下所示。这三个函数分别从字符串,unicode对象以及long值生成PyIntObject对象。其中PyInt_FromUnicode最终调用PyInt_FromString,而PyInt_String最终调用PyInt_FromLong函数,因此我这里先简单分析下PyInt_FromLong函数。

//Includes/intobject.h中
(PyObject *) PyInt_FromString(char*, char**, int);
(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
(PyObject *) PyInt_FromLong(long);   
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) { //PyIntBlock的objects没有空闲空间或者第一次分配时,调用fill_free_list函数分配PyIntBlock
        if ((free_list = fill_free_list()) == NULL)                                                                                                            
            return NULL;
    }    
    /* Inline PyObject_New */
    v = free_list;
    free_list = (PyIntObject *)v->ob_type; //更新free_list,指向PyIntBlock的objects的下一个对象
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}

这里涉及到小整数和PyIntBlock相关内容,在创建PyIntObject对象的时候,会首先判断数值大小是否在小整数范围内,如果在,则直接从小整数对象池small_ints中取。其中small_ints是在python初始化的时候调用_PyInt_Init函数创建的,代码如下:

#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];
};

typedef struct _intblock PyIntBlock;

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

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();
     
     /*通过next指针链接PyIntBlock,最新分配的插入到链表头部,block_list总是指向最新分配的PyIntBlock*/   
    ((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.
       将PyIntBlock的objects数组转换为单向链表,从后往前通过ob_type连接,并返回最后一个对象。
        */
    p = &((PyIntBlock *)p)->objects[0];
    q = p + N_INTOBJECTS;
    while (--q > p)
        q->ob_type = (struct _typeobject *)(q-1);
    q->ob_type = NULL;
    return p + N_INTOBJECTS - 1;
}

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 *)v->ob_type; // small_ints数组存储小整数对象,注意下small_ints[0]存储的是-5,small_ints[5]存储的才是0.free_list从后往前通过ob_type链接,每次都是从后面取。
        PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        small_ints[ival + NSMALLNEGINTS] = v;
    }
#endif
    return 1;
}

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


4 调试PyIntObject

按照《Python源码剖析》中的方法,可以打印出一些调试信息。先是修改int_print函数对象,来打印小整数对象和block_list,free_list等相关信息。修改Objects/intobject.c中的int_print函数,代码如下:

static int values[10];
static PyIntObject** addr[10];
static int refcounts[10];
/* ARGSUSED */
static int
int_print(PyIntObject *v, FILE *fp, int flags)
     /* flags -- not used but required by interface */
{
    //fprintf(fp, "%ld", v->ob_ival);
    PyIntObject *intObjectPtr;
    PyIntBlock *p = block_list;
    PyIntBlock *last = NULL;
    int count = 0;
    int i;

    while (p != NULL) 
    {
        ++count;
        last = p;
        p = p->next;
    }

    intObjectPtr = last->objects;
    intObjectPtr += N_INTOBJECTS - 1;
    printf(" address @%p, value=0x%x\n", v, v->ob_ival);

    for (i=0; i<10; ++i, --intObjectPtr)
    {
        values[i] = intObjectPtr->ob_ival;
        refcounts[i] = intObjectPtr->ob_refcnt;
        addr[i] = intObjectPtr;
    }

    printf(" value: ");
    for (i=0; i < 8; ++i)
    {
        printf("%d:%p\t", values[i], addr[i]);
    }
    printf("\n");

    printf(" refcnt: ");
    for (i=0; i < 8; ++i)
    {
        printf("%d\t", refcounts[i]);
    }
    printf("\n");

    printf("block_list count: %d\n", count);
    printf("free_list: %p\n", free_list);

    return 0;
}

编译源码后运行python,测试的结果如下:

>>> a = -12345
>>> a
 address @0x9415790, value=0xffffcfc7
 value: -5:0x9414f80    -4:0x9414f74    -3:0x9414f68    -2:0x9414f5c    -1:0x9414f50    0:0x9414f44 1:0x9414f38 2:0x9414f2c 
 refcnt: 1  1   2   1   32  109 49  30  
block_list count: 4
free_list: 0x941579c

>>> b = -12345
>>> b
 address @0x941579c, value=0xffffcfc7
 value: -5:0x9414f80    -4:0x9414f74    -3:0x9414f68    -2:0x9414f5c    -1:0x9414f50    0:0x9414f44 1:0x9414f38 2:0x9414f2c 
 refcnt: 1  1   2   1   32  109 49  30  
block_list count: 4
free_list: 0x94157b4

>>> c = -5
>>> c
 address @0x9414f80, value=0xfffffffb
 value: -5:0x9414f80    -4:0x9414f74    -3:0x9414f68    -2:0x9414f5c    -1:0x9414f50    0:0x9414f44 1:0x9414f38 2:0x9414f2c 
 refcnt: 5  1   2   1   32  109 49  30  
block_list count: 4
free_list: 0x94157b4

>>> d = -5
>>> d
 address @0x9414f80, value=0xfffffffb
 value: -5:0x9414f80    -4:0x9414f74    -3:0x9414f68    -2:0x9414f5c    -1:0x9414f50    0:0x9414f44 1:0x9414f38 2:0x9414f2c 
 refcnt: 6  1   2   1   32  109 49  30  
block_list count: 4
free_list: 0x94157b4

通过测试可以发现,a和b虽然值都是-12345,但是他们是两个不同的对象,地址也不一样。c和d都是-5,但是由于小整数缓存机制,所以c和d的地址是一样,是同一个对象。同时我们可以观察到小整数中-5到2这8个整数的地址是从高到低的,相隔12个字节,这也就验证了objects数组是从后往前通过ob_type字段连接成链表的。

另外,我们可以用id(xxx)来获取对象的地址(当然这个地址是指逻辑地址),比如上面例子中的id(d)的结果就0x9414f80。id对应的代码在Python/bltinmodule.c的builtin_id(PyObject *self, PyObject *v)函数,其功能就是打印出python对象的地址。同理,id(int)就是PyInt_Type类型对象的地址。

5 总结

简单总结下,Python维护多个PyIntBlock对象,一个PyIntBlock中存储多个整数。PyIntBlock之间通过链表连接,最新分配的PyIntBlock加入在链表首部,block_list为链表首部。而PyIntBlock中的整数对象数组objects通过ob_type指针从后往前链接,freelist为该链表首部,即objects数组的最后一个对象。

整数对象引用减少到0时,调用int_dealloc函数释放对象。需要注意的是,PyIntObject释放对象的时候,并不释放内存,只是将这块内存作为可用内存加入到free_list中,并将free_list指向刚刚释放的对象。

6 参考资料

陈儒-《python源码剖析》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,326评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,228评论 1 304
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,979评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,489评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,894评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,900评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,075评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,803评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,565评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,778评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,255评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,582评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,254评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,151评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,952评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,035评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,839评论 2 277

推荐阅读更多精彩内容

  • http://python.jobbole.com/85231/ 关于专业技能写完项目接着写写一名3年工作经验的J...
    燕京博士阅读 7,486评论 1 118
  • 1.元类 1.1.1类也是对象 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这...
    TENG书阅读 1,211评论 0 3
  • 整数对象 PyIntObject PyIntObject 是一个值不可变对象 定义 相应的与整数类型相对应的类型对...
    阿布吃de饭阅读 685评论 0 0
  • 个人笔记,方便自己查阅使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik阅读 67,642评论 0 5
  • “墙上的斑点” 我第一次注意到短裤上的那个破洞,大概是在金年的三月上旬。如果想要知道具体的时间,那就得回想一下当时...
    freenik阅读 750评论 0 4