Quantcast
Channel: CodeSection,代码区,Python开发技术文章_教程 - CodeSec
Viewing all articles
Browse latest Browse all 9596

CPython实现 - 整型对象 - 实现分析

$
0
0

这篇文档主要介绍Cpython中的整数对象

在这之前先贴一些Python代码

>>> id(1) 140574362462680 >>> id(2) 140574362462656 >>> id(3) 140574362462632 >>> id(4) 140574362462608

内存地址竟然是连续的

id(1) - 24 = id(2) id(2) - 24 = id(3) id(3) - 24 = id(4)

天啦噜

一句话介绍:python整数对象的值是一个c语言的long类型

我也是看到哪写到哪里,请随意看看

上来先贴一个int内存池的初始化方法

简单看一下,看不懂也没有关系,重点看字

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) q->ob_type = (struct _typeobject *)(q-1); q->ob_type = NULL; return p + N_INTOBJECTS - 1; }

可以看到PyIntObject指针p指向了刚分配的PyIntBlock的起始地址

这里把PyIntObject对象指针强制转为了PyIntBlock指针

这里的强制转换是可以的,因为分配的内存大小本来就是PyIntBlock

那么什么是PyIntBlock

来看一下PyIntBlock的结构

#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里有一个 PyIntObject的数组object

看起来PyIntBlock是一个PyIntObject的集合

还有一个next指针

next指针说明这个结构体主要作为单链表使用

#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject)) sizeof(PyIntObject)在我的电脑上为24 (Macbook Pro mid 2015)

可通过以下python代码查看

import sys sys.getsizeof(1) >>> 24

所以objects的大小为41

也是就是说每个PyIntBlock包含41个PyIntObject

我们还看到了

block_list

free_list

这两个是什么呢,只能接着往下看

来扫一眼这堆宏

#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 #ifdef COUNT_ALLOCS int quick_int_allocs, quick_neg_int_allocs; #endif

定义了NSMALLPOSINTS(n small positive ints) 257

NSMALLNEGINTS(n small negative ints) 5

然后定义了一个 small_ints指针,指向大小为(257+5)的PyIntObject数组

small_ints 为指针数组(数组中存放的都是指针)

现在是创建PyIntObject的函数

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 */ v = free_list; free_list = (PyIntObject *)v->ob_type; PyObject_INIT(v, &PyInt_Type); v->ob_ival = ival; return (PyObject *) v; } 这个函数先判断一下这个long类型变量ival是否在[-5,257)范围内

如果在的话直接从small_ints这个指针数组中取出一个指向PyIntObject的指针,并且将该指针指向对象的引用计数加1

如果不在的话判断一下free_list是否为空,为空的话

执行 free_list = fill_free_list()

也就是到了我们最开始的那个函数

这个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) q->ob_type = (struct _typeobject *)(q-1); q->ob_type = NULL; return p + N_INTOBJECTS - 1; }

p开始是一个PyIntObject指针

然后申请了一个sizeof(PyIntBlock)空间

p指向新申请空间的起始地址

然后p被转换为了一个PyIntBlock指针

然后p->next指向了block_list的起始地址

然后block_list指向了p

也就是说现在的block_list已经有一个PyIntBlock的内存空间了

接下来做了些什么?

接下来p又指向了刚分配内存空间的PyIntBlock结构体objects数组的第一个PyIntObject起始地址

q则指向了objects数组最后一个元素的起始地址

接下来据说是把objects中的PyIntObject对象依次用ob_type指针链接起来

但是现在问题是ob_type是个什么玩意?

这个时候来回顾一下PyIntObject

typedef struct _object { PyObject_HEAD } PyObject; #define PyObject_HEAD \ _PyObject_HEAD_EXTRA \ Py_ssize_t ob_refcnt; \ struct _typeobject *ob_type; typedef struct { PyObject_HEAD long ob_ival; } PyIntObject;

现在可以看到ob_type是指向_typeobject结构体的指针

所以我们还得看一下――typeobject是什么,我把代码贴过来

天啦噜 代码太长了不贴了,反正已经知道是指针了 天下指针都一样

下面着重分一下这两行代码干了什么

while (--q > p) q->ob_type = (struct _typeobject *)(q-1);

指针q指向一个PyIntObject对象,

现在让q指向的对象的ob_type指针指向q前面一个元素

这样下来所有的PyIntBlock的objects内的所有PyIntObject通过ob_type链接起来成为了一个单向链表

q->ob_type = NULL; //处理第一元素

最后返回了最后一个PyIntObject元素的地址

这个地址也就是free_list的起始地址

这样的话free_list就有了空间

其实就是free_list分配空间的过程

接下来回到

PyInt_FromLong这个函数

为了看起来方便,我把代码贴下来

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 */ v = free_list; free_list = (PyIntObject *)v->ob_type; PyObject_INIT(v, &PyInt_Type); v->ob_ival = ival; return (PyObject *) v; }

现在

v = free_list;

这里v就是指向刚分配的PyIntBlock的objects数组的最后一个元素

然后free_list沿着链表指向了objects中的前一个元素

现在调用了PyObject_INIT()函数,先不管这个函数,就当它是初始化好了

然后给v指向的PyIntObject的ob_ival赋值

最后返回一个PyObject对象的指针

注意哦 这里不是PyIntObject

从函数定义上也能看出来

PyObject * PyInt_FromLong(long ival)

至于是为什么,我知道了再更新在下面

现在呢当你调用了PyInt_FromLong方法之后

就会得到一个指向个这个整数PyIntObject的指针

现在我们就知道当生成一个大整数对象的时候要从free_list中拿到分配的空间

只有当free_list == NULL的时候才会重新申请一个PyIntBlock

之前提到了block_list

什么是block_list?

((PyIntBlock *)p)->next = block_list; block_list = (PyIntBlock *)p; static PyIntBlock *block_list = NULL; static PyIntObject *free_list = NULL;

free_list是一个PyIntObject链表

block_list是一个PyIntBlock链表

每次分配的PyIntBlock都会加入到block_list

free_list则指向PyIntBlock objects中的PyIntObject

block_list指向的永远是最近创建的PyIntBlock

这个时候出现重大问题

block_list 这个链表中一旦有其中一个PyIntBlock中的objects中的PyIntObject需要被释放,那怎么办?

答案是python在回收的时候把空闲的全都链接到了一起

那么链接到什么地方去了

空闲空间怎么复用

原有objects链表如何链接?

只能继续往下看

PyIntObject的销毁

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

大致扫一眼就知道了

回收的PyIntObject对象空间被放到了free_list的链表的头部

v->ob_type = (struct _typeobject *)free_list; free_list = v;

也就是说这部分内存并没有被释放

这里请注意,永远没有被释放

一个PyIntObject 24个字节

1GB内存 理论上可以容纳 44739242 个整数对象,实际更少

那么 大整数就是这样了 做个简单总结

当要生成一个大整数对象的时候

先去free_list中取空间

没有就初始化free_list

每一次初始化就会申请一个PyIntBlock的空间

空间大小为1000-8 = 992个字节(992B)

这些空间为41个整数

所有的PyIntBlock都在block_list这个链表上

而free_list则是所有这些PyIntBlock中可用PyIntObject的链表

当空间需要回收的时候,就把这个空间放到free_list链表的头部,以备下次使用

现在关注一下小整数对象池small_ints

static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; 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; PyObject_INIT(v, &PyInt_Type); v->ob_ival = ival; small_ints[ival + NSMALLNEGINTS] = v; } #endif return 1; }

这段代码还是很清晰的,

从free_list中取空间,然后挨个初始化[-5,257)中所有的整数对象

然后加入small_ints这个指针数组中,下标就是对应的整数值

这里值得注意的是申请空间同样是通过fill_free_list实现

也就是说小整数对象也是在block_list中的

最后总结

当使用python整数的时候,申请空间不是24B一个一个申请的,而是一次申请992B

也就是说一次申请41个PyIntObject的空间

而小整数[-5, 257) 永远占据着内存空间,并且在Python启动的时候就完成初始化了

并且之后申请的整数空间Python也不会释放,而是放到整数空间池free_list里

就这样直到地老天荒~

想要试一下内存占用是否真的那么夸张?

来执行下下面代码

for i in range(44739242): pass

在系统监视器或者任务管理器里面查看一下内存占用

~= 1GB

EOF

参考文档

[1]. 《Python源码剖析》

本作品采用 知识共享署名-相同方式共享 3.0 未本地化版本许可协议 进行许可


Viewing all articles
Browse latest Browse all 9596

Trending Articles