python对象位于堆内存_Python 整数对象的内存管理

Python整数的内存管理

上篇文章我们阐述了Python对整数对象的设计后,我们会发现,大量操作后会产生大量的PyIntObject,占据大量的堆空间。更有甚者,当我们试图在诸如for循环之类的操作中快速大量创建临时性整数时,会频繁申请堆内存从而引发大量的内存碎片,可能对整体性能产生出来严重的影响。

因此,这篇文章我们一起来探究下Python究竟是如何优化这些负面影响的。

整数的区分

对密码学有研究的人都知道,英文字母总共有26个,但是他们在日常生活(比如写作)中出现的频率却各不同,比如e可能是字母中出现频次最高的一个。

而在我们日常编程过程中,虽然整数的使用无处不在,但是却有一部分数字明显会比其余的频次高出许多,大致举几个例子:

for i in range(0, 10)

a + b > 0

y = 7a + 8b + 3c + 2z

a + b > INT_MAX

不难看出,基本的逻辑循环、分支控制以及运算、越界判断等等,常常会和部分数字打交道。上文《Python的整数设计》中我们知道,整数的每次运算等操作始终都会创建一个新的对象,那么如果我们这些经常遇到的数字都需要多次创建分配,那势必造成大量的内存调用,不仅仅影响性能,还会产生大量的内存碎片。

那么,有什么比较好的解决思路吗?

可能有些朋友已经会想到,既然这些数字经常用到,那我们建立一个缓存池不就好了吗?

没错!Python自身也是这么设计。但是随之而来的还有几个问题,

缓存哪些数字?

使用何种方式进行缓存?

Python的设计是将数字区分为小整数和大整数对象。对于小整数对象,采用数组直接全部缓存,而对于大整数对象,采用链表的方式循环复用特定个数的内存池。

小整数缓存池

首先我们先来探究下全部缓存的小整数对象,想必各位可以一样都有困惑,究竟哪些整数可以归为小整数呢?

ifndef NSMALLPOSINTS

#define NSMALLPOSINTS 257

#endif

#ifndef NSMALLNEGINTS

#define NSMALLNEGINTS 5

#endif

#if NSMALLNEGINTS + NSMALLPOSINTS > 0

// 缓存池

static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

#endif

从[intobject.c]中我们可以发现,Python源代码将-5(包含)到257(不包含)这段连续区间的数字定义为小整数,利用指针数组全部进行了缓存,而数组的index直接可以快速索引,也避免了单独构建映射表的内存开销。

还有一点需要注意,小整数的缓存并不是通常所认为的懒加载的构造方式。而是Python在运行时初始化的时候,一次性直接初始化构建完:

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 *)Py_TYPE(v);

(void)PyObject_INIT(v, &PyInt_Type);

v->ob_ival = ival;

small_ints[ival + NSMALLNEGINTS] = v;

}

#endif

return 1;

}

大整数缓存池

谈完了小整数的缓存池设计,我们再来谈谈那些大整数的设计。Python对大整数并未采取对象复用的策略,但是在减少内存申请和碎片化优化上还是做了不少设计。Python自建了一个内存池的概念,当通用对象不在使用(引用计数为0),所占用的内存继续利用;除此之外,当内存池容量不够的时候,是以固定个数向系统申请一批内存。

// 结构定义

#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();

((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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值