Python 整数对象的实现

2015-11-01  本文已影响677人  超net

Python 的内建对象存放在源代码的Objects目录下。
intobject.c用于整数对象

在 Python 中,整数分为小整数对象大整数对象

小整数对象

由于数值较小的整数对象在内存中会很频繁地使用,如果每次都向内存申请空间、请求释放,会严重影响 Python 的性能。好在 整数对象 属于不可变对象,可以被共享而不会被修改导致问题,所以为 小整数对象 划定一个范围,即小整数对象池,在Python运行时初始化并创建范围内的所有整数,这个范围内的 整数对象是被共享的,即一次创建,多次共享引用。

那么这个范围是多少呢?从源文件中可以看到,而且,用户可以自行调整,只是每次都要在源文件中修改,而后进行编译、安装。

小整数池的范围:

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

从源代码可以看出
define NSMALLPOSINTS 257,范围的右边界
define NSMALLNEGINTS 5,范围的左边界
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive),[-5, 257)

大整数对象

但是,整数对象很多,不一定都是小整数对象,又不能将所有整数对象都放入内存。于是,Python 提供了一个可扩展的内存空间,称为通用整数对象池,谁需要用就给谁用,这样免去了申请空间,又能提高一些效率。

这个空间是一个PyIntBlock结构,是用一个单向列表连接一串内存(block),这个列表由block_list维护,而每个 block 维护一个 整数对象数组(Objects),用于存放被缓存的整数对象。block_list的内容是最新创建的 block。
小整数对象池 也在block_list上。

Python 使用一个单向链表管理全部 block 的 objects 中的所有空闲内存,由free_list指出下一个可用的空闲内存。如果当前没有空闲内存,free_listNULL,会创建新的内存。

当整数对象的引用计数变为0,会销毁对象,但并不会释放空闲出来的内存,即将内存交还系统,而是重新加入free_list

hack

使用Xcode修改打印整数对象的方法

原始文件

/* ARGSUSED */
static int
int_print(PyIntObject *v, FILE *fp, int flags)
     /* flags -- not used but required by interface */
{
    long int_val = v->ob_ival;
    Py_BEGIN_ALLOW_THREADS
    fprintf(fp, "%ld", int_val);
    Py_END_ALLOW_THREADS
    return 0;
}

修改后,可以打印部分小整数地址池中,整数对象的引用次数、所在内存地址、下一个可用的空闲内存地址

/* ARGSUSED */

static int values[10];
static int refcounts[10];

static int
int_print(PyIntObject *v, FILE *fp, int flags)
     /* flags -- not used but required by interface */
{
    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\n", v);
    
    for(i = 0; i < 10; ++i, -- intObjectPtr)
    {
        values[i] = intObjectPtr -> ob_ival;
        refcounts[i] = intObjectPtr -> ob_refcnt;
    }
    printf(" value : ");
    for(i = 0; i < 8; ++i)
    {
        printf("%d\t", values[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;
/*    long int_val = v->ob_ival;
    Py_BEGIN_ALLOW_THREADS
    fprintf(fp, "%ld", int_val);
    Py_END_ALLOW_THREADS
    return 0;*/
}

保存并编译、安装,运行修改后的 Python

Python 2.6.9 (unknown, Nov  1 2015, 20:22:05)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.1.76)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> i = -9999
>>> i
 address @0x7fe412f16470       # -9999 所在的内存地址
 value : -5 -4  -3  -2  -1  0   1   2   # 能够显示的 -5 ~ 2
 refcnt : 1 1   1   1   35  105 64  41     # 引用计数器,可以看到,小整数已经被Python自身使用了多次
 block_list count : 8       # block 数量
 free_list : 0x7fe412f16488     # 下一个可用的空闲内存地址

>>>
>>> a = -258
>>> a
 address @0x7fe412f16488     # -258 的内存地址,是上面`free_list`指出的空闲内存
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 1 1   1   1   35  105 64  41
 block_list count : 8
 free_list : 0x7fe412f164a0    # 新的空闲内存地址

>>> b = -258
>>> b
 address @0x7fe412f164a0    # 上一个的空闲内存地址,可以看出,对于多次创建的大整数对象,即使值一样,也是不同的内存地址
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 1 1   1   1   35  105 64  41
 block_list count : 8
 free_list : 0x7fe412f164b8

>>> del b               # 释放 b 的内存空间
>>> a
 address @0x7fe412f16488
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 1 1   1   1   35  105 64  41
 block_list count : 8
 free_list : 0x7fe412f164a0    # 删除 b 后,新的空闲内存重新加入`free_list`,没有归还给系统

>>> c1 = -5          # 属于小整数对象池
>>> c1
 address @0x7fe412f033d8
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 5 1   1   1   35  105 64  41   # -5 引用此时为 5
 block_list count : 8
 free_list : 0x7fe412f164b8

>>> c2 = -5        # 同上
>>> c2
 address @0x7fe412f033d8      # 两次创建的相同小整数对象,指向了相同的内存地址
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 6 1   1   1   35  105 64  41     # -5 的引用次数加一,变为 6
 block_list count : 8
 free_list : 0x7fe412f164b8

>>>

整数对象的说明文件内置在源代码中:

PyDoc_STRVAR(int_doc,
"int(x[, base]) -> integer\n\
\n\
Convert a string or number to an integer, if possible.  A floating point\n\
argument will be truncated towards zero (this does not include a string\n\
representation of a floating point number!)  When converting a string, use\n\
the optional base.  It is an error to supply a base when converting a\n\
non-string.  If base is zero, the proper base is guessed based on the\n\
string content.  If the argument is outside the integer range a\n\
long object will be returned instead.");

int_doc就是整数对象的__doc__属性

参考资料

《Python 源码剖析》第二章:整数对象

上一篇下一篇

猜你喜欢

热点阅读