• Python源代码--整数对象(PyIntObject)的内存池


    【背景】

    原文链接:http://blog.csdn.net/ordeder/article/details/25343633

    Python整数对象是不可变对象,什么意思呢?比如运行例如以下python语句
    >>>a = 1023
    >>>a = 1024
    >>>b = a
    >>>c = 1024
    >>>d = 195
    >>>e = 195

    python的整数对象结构为:
    typedef struct {  
        PyObject_HEAD   
        long ob_ival;  
    } PyIntObject;

    第一条命令运行后。python vm 创建了一个PyIntObject A,当中的ob_ival=1023记录了该整数对象的值,名字a引用该对象。即A 的 ob_refcnt=1。
    当运行第二条语句的时候。python vm 又建立了新的PyIntObject B,其ob_ival值为1024.且名字a解引用AA的ob_refcnt-1变成0,系统将其回收。名字a引用对象B,B的ob_refcnt=1;
    运行第三条语句,名字b引用名字a引用的对象,故而B的ob_refcnt+1。即为2。
    第四条语句:c引用了不同于B的还有一个整数对象
    第五条语句:d引用了小整数对象195
    第六条语句: e和d引用的是同一个对象。及小整数对象
    p.s. 小整数的范围为[-5,257)

    在python中的PyIntObject对象ob_ival内容是不可变的。

    【Python中整数对象的存储优化】

    因为python中的整数对象记录的整数值是不可变的,所以在名字a的值不断变化的过程中。就就涉及到了多次对象的创建和销毁。

    所以python为整数对象申请空间进行了两种优化:
    优化1:为通用整数对象存储池
    优化2:为小整数对象构建特殊的缓冲

            PyIntObject分为小整数对象[-5~257)及大整数对象。小整数对象在py启动过程中初始化。从而实现小整数对象的缓存,缓冲中的小整数对象在py执行期间不会被销毁。        大整数对象须要程序猿动态申请,对象在执行过程中依据ob_refcnt引用计数确定是否销毁(计数为0)。

           其次。py为了优化整数对象的申请工作。为大整数对象引入了缓冲池的概念。为何引入缓冲池?我的理解是:对于系统来说。alloc一个PyIntObject对象,须要一次系统调用,为了避免每次创建对象都去调用alloc,便引入整数缓冲池的概念。

    【小整数缓冲】

      看着名字感觉挺奇妙。事实上就是在vm启动的时候预先将[-5~257)这些整数构建对应的整数对象。

    这些整数
    对象的构建所在的内存空间相同是在:通用整数对象的缓冲池。

    仅仅只是这些个小整数对象的ob_refcnt不会改变
    且永远>0,所以在vm执行过程中不会被销毁。所以起到了缓冲的作用。

    【通用整数对象的缓冲池】

           为了降低alloc系统调用申请空间,内存池一次性申请的空间不是当个PyIntObject大小,而是一个以PyIntBlock块为结构的大小的空间,每一个PyIntBlock块容纳了n个PyIntObject对象。内存池的基本数据结构例如以下:

    #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;
     系统在启动的时候。PyIntBlock *block_list为空的,在执行过程中,假设须要创建整数对象,系统会先判定block_list是否有空暇的空间供创建对象,通过fill_free_list()函数从缓冲池中获取可用的PyIntObject。
      假设free_list有空暇的PyIntObject可用,则直接在缓冲池中获取该空暇空间,你懂得。


      假设没得,系统将通过alloc申请一个PyIntBlock挂入block_list中,同一时候将该块分为N_INTOBJECTS整数对象PyIntObject挂入到free_list中。

    1. fill_free_list()的函数实现

    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); //[1]
        Py_TYPE(q) = NULL;
        return p + N_INTOBJECTS - 1;
    }
    说明[1]
    py将PyIntObject->ob_type作为free_list的暂时next指针,使用了指针强制转换。尽管破坏了指针的安全原则。可是重用了>ob_type内存空间。不失为一种好方法!下图描绘了两个PyIntBlock构成的通用整数缓冲池:


    2. 其余两个构建和删除整数对象相关函数:

    //构建intobj
    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) { //[1]
            if ((free_list = fill_free_list()) == NULL)
                return NULL;
        }
        /* Inline PyObject_New */
        v = free_list;
        //[2]
        free_list = (PyIntObject *)Py_TYPE(v); 
        PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        return (PyObject *) v;
    }
    [1]缓冲池的空暇链表为空,通过fill_free_list()去申请新的PyIntBlock
    [2](PyIntObject *)Py_TYPE(v)相当于是PyIntObject在free_list中的next指针。
    
    //删除intobj
    static void int_dealloc(PyIntObject *v)
    {
        if (PyInt_CheckExact(v)) { //[1]
            Py_TYPE(v) = (struct _typeobject *)free_list;
            free_list = v;
        }
        else //[2]
            Py_TYPE(v)->tp_free((PyObject *)v);
    }
    [1] 判定假设v的引用计数为1(经过本次解引用变为0)。则将该PyIntObject空间增加到缓冲池的空暇队列。以便重用
    [2]引用计数>2 将该对象引用计数减1
    


  • 相关阅读:
    PHP使用http_build_query()构造URL字符串的方法
    php将一个二维数组按照某个字段值合并成一维数组,如果有重复则将重复的合并成二维数组
    资金管理
    偏爱粉色,我的儿子会不会娘娘腔?
    中文期刊有哪些?
    超声胎儿图像分割
    加州wonders教材扫盲
    美国小学1-5年级教学大纲
    A股回归牛市?
    深入研究股票涨停
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/5260468.html
Copyright © 2020-2023  润新知