python内存分配_Python进阶2-元组和列表的内存分配机制
本系列文章是一系列学习笔记,希望较为深入地分析Python3中的原理、性能,文章中绝大部分观点都是原作作者的观点(如下),本人对书中示例加以实践和总结,并结合相应的Python的C语言源码(3.6.1),分享出来。原著:
《High Performance Python》by O'Relly Media,作者Micha Gorelick,Ian Ozsvald
《Fluent Python》by O'Relly Media,作者Luciano Ramalho
从内存利用和CPU利用开始了解List和Tuple的优缺点
定义
List:动态数组,元素可变,可改变大小(append,resize)
Tuple:静态数组,不可变,数据一旦创建后不可改变
List的内存利用
当创建N个元素的List时,Python的动态内存分配长N+1个元素的内存,第一个元素存储列表长度,和列表的元信息。
当Append一个元素时,Python将创建一个足够大的列表,来容纳N个元素和将要被追加的元素。重新创建的列表长度大于N+1(虽然我们只触发一次append操作),实际上,为了未来的Append操作,M个元素长度(M>N+1)的内存将会被额外分配,然后,旧列表中的数据被copy到新列表中,旧列表销毁。
额外内存的分配,只会发生在第一次Append操作时,当我们创建普通列表时,不会额外分配内存。
这里的哲学是,一个Append操作很可能是很多Append操作的开始,通过额外分配内存来减少可能的内存分配和内存copy的次数。
那么,对于一个具有N个元素的列表,当一次Append操作发生时,新列表要分配多少内存(额外M个元素,需多分配一个元素存储长度)呢?答案是:
** M = (N >> 3) + (N <9 ? 3 : 6) + 1 **
我们来看Python3.6.1的列表resize过程,源代码位于Objects/listobject.c中的list_resize函数:
static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
PyObject **items;
size_t new_allocated;
Py_ssize_t allocated = self->allocated;
/* Bypass realloc() when a previous overallocation is large enough
to accommodate the newsize. If the newsize falls lower than half
the allocated size, then proceed with the realloc() to shrink the list.
*/
if (allocated >= newsize && newsize >= (allocated >> 1)) {
assert(self->ob_item != NULL || newsize == 0);
Py_SIZE(self) = newsize;
return 0;
}
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
*/
new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
/* check for integer overflow */
if (new_allocated > SIZE_MAX - newsize) {
PyErr_NoMemory();
return -1;
} else {
new_allocated += newsize;
}
if (newsize == 0)
new_allocated = 0;
items = self->ob_item;
if (new_allocated <= (SIZE_MAX / sizeof(PyObject *)))
PyMem_RESIZE(items, PyObject *, new_allocated);
else
items = NULL;
if (items == NULL) {
PyErr_NoMemory();
return -1;
}
self->ob_item = items;
Py_SIZE(self) = newsize;
self->allocated = new_allocated;
return 0;
}
结合C源码我们来举个例子,当一个list长度为8时,发生append操作后:
1)new_size = 原有的size + append一个对象 = 8 + 1 = 9
2)newsize为9,二进制是 1001,9 >> 3 = 1
3)new_allocated = 9 >> 3 + 6 = 7
4)new_allocated += new_size,为9 + 7 = 16
4)列表的最终大小为Py_SIZE = 16
Tuple的内存利用
虽然Tuple不支持resize,但是我们可以粘贴两个元祖组成一个新的元组,这个操作类似于List的append,但是又不会额外的分配内存。但我们不能把它当成append,因为每次都会进行一个分配内存和内存copy操作。
另一个Tuple的静态本质带来的好处是,resource caching。Python是garbage collected,当一个变量不用了,内存会被回收并交回给OS。但是,对于一个20个元素的Tuple,当它不再被用时,内存不会立即返还给OS,而是为了以后应用而暂缓保留,当一个新的Tuple被创建时,我们不会向OS重新申请分配内存,而是用现有reserved的free memory。
也就是,Tuple的创建很简单并且避免频繁与OS申请内存,创建一个具有10个元素的Tuple比创建一个List要快不少,55ns VS 280 ns。
我们可以通过Python源码看到上面的结论,代码位于Objects/tupleobject.c,我们可以清楚的看到tuple的粘贴过程:
新的大小等于两个tuple大小之和
重新分配内存
对于分配好的新内存,通过两个for循环将原来的两个元组拷贝到新的元组上
static PyObject *
tupleconcat(PyTupleObject *a, PyObject *bb)
{
Py_ssize_t size;
Py_ssize_t i;
PyObject **src, **dest;
PyTupleObject *np;
if (!PyTuple_Check(bb)) {
PyErr_Format(PyExc_TypeError,
"can only concatenate tuple (not \"%.200s\") to tuple",
Py_TYPE(bb)->tp_name);
return NULL;
}
#define b ((PyTupleObject *)bb)
if (Py_SIZE(a) > PY_SSIZE_T_MAX - Py_SIZE(b))
return PyErr_NoMemory();
size = Py_SIZE(a) + Py_SIZE(b);
np = (PyTupleObject *) PyTuple_New(size);
if (np == NULL) {
return NULL;
}
src = a->ob_item;
dest = np->ob_item;
for (i = 0; i < Py_SIZE(a); i++) {
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
src = b->ob_item;
dest = np->ob_item + Py_SIZE(a);
for (i = 0; i < Py_SIZE(b); i++) {
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
return (PyObject *)np;
#undef b
}
在分配内存函数PyTuple_New中,当大小小于20时,Python会直接从一个空闲的内存表中拿出来,不会重新申请,这减少了小元组的内存访问次数,宏PyTuple_MAXSAVESIZE为20
PyObject *
PyTuple_New(Py_ssize_t size)
{
PyTupleObject *op;
Py_ssize_t i;
if (size < 0) {
PyErr_BadInternalCall();
return NULL;
}
#if PyTuple_MAXSAVESIZE > 0
if (size == 0 && free_list[0]) {
op = free_list[0];
Py_INCREF(op);
#ifdef COUNT_ALLOCS
tuple_zero_allocs++;
#endif
return (PyObject *) op;
}
if (size < PyTuple_MAXSAVESIZE && (op = free_list[size]) != NULL) {
free_list[size] = (PyTupleObject *) op->ob_item[0];
numfree[size]--;
#ifdef COUNT_ALLOCS
fast_tuple_allocs++;
#endif
/* Inline PyObject_InitVar */
#ifdef Py_TRACE_REFS
Py_SIZE(op) = size;
Py_TYPE(op) = &PyTuple_Type;
#endif
_Py_NewReference((PyObject *)op);
}
else
#endif
{
/* Check for overflow */
if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - sizeof(PyTupleObject) -
sizeof(PyObject *)) / sizeof(PyObject *)) {
return PyErr_NoMemory();
}
op = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size);
if (op == NULL)
return NULL;
}
for (i=0; i < size; i++)
op->ob_item[i] = NULL;
#if PyTuple_MAXSAVESIZE > 0
if (size == 0) {
free_list[0] = op;
++numfree[0];
Py_INCREF(op); /* extra INCREF so that this is never freed */
}
#endif
#ifdef SHOW_TRACK_COUNT
count_tracked++;
#endif
_PyObject_GC_TRACK(op);
return (PyObject *) op;
}
python内存分配_Python进阶2-元组和列表的内存分配机制相关推荐
- 2023年Python面试题_Python进阶_48道
Python 中类方法.类实例方法.静态方法有何区别? 类方法:是类对象的方法,在定义时需要在上方使用"@classmethod"进行装饰,形参为 cls,表示类对象,类对象和实例 ...
- python核心数据类型_Python核心数据类型—元组
Python核心数据类型-元组 Python元组与列表类似,但是元组属于不可变类型 创建元组 a = () #创建空元组 a = (1, 2, 3) #创建一个元组 a = [1, 2, 3] b = ...
- python如何返回一个列表_python如何返回元组,列表或字典的?
Python中的数据结构用于存储数据集合,这些数据可以从函数中返回.那么python如何返回元组,列表或字典的?在本文中,我们将探讨如何从这些数据结构中返回多个值:元组,列表和字典.您可以从Pytho ...
- python列表元组字典相互转化_python中字典元组和列表的互相转化
转自:http://www.cnblogs.com/linjiqin/p/3674356.html #-*-coding:utf-8-*- #1.字典 dict = {'name': 'Zara', ...
- python paramiko并发_python学习笔记9--paramiko模块、多线程、锁机制
一.paramiko模块 paramiko模块是一个遵循ssh2协议的python扩展模块,该模块可以允许使用python通过ssh协议去远程管理主机.在使用该模块前,需要手动安装,具体安装过程请百度 ...
- python元组与列表的区别、简答题_细解python面试题(一)元组和列表的区别
可能略去了一些内容. 1.列表VS元组 在python编程中,我们经常搞不懂列表和元组的区别,或者哪些函数可以用于列表,哪些用于元组,那么跟随本文我们来深入的探索. 2.元组 元组是值的集合,我们用圆 ...
- python申请内存函数_python进阶用法2 【从帮助函数看python内存申请机制】
前言 介绍了四个帮助函数,dir(),help(),type(),id(),通过id()函数进一步分析了python在申请内存方面的效率问题,提到的基本类型有string,list,queue和deq ...
- python列表嵌套元组拆分_Python进阶之元组拆包及嵌套元组拆包
1.元组拆包 元组拆包也叫可迭代元素拆包 元组拆包的基本用法: >>> lax_coordinates = (33.9425, -118.408056) >>> l ...
- print python 如何加锁_Python 进阶(一):多线程
1. 相关概念 1.1 解释器 Python 解释器的主要作用是将我们在 .py 文件中写好的代码交给机器去执行,比较常见的解释器包括如下几种: CPython:官方解释器,我们从官网下载安装后获得的 ...
最新文章
- 2011-2012年《软件工程》本科期末试卷
- 按照Right-BICEP要求对实验二进行测试
- 从2021年多篇顶会论文看OOD泛化新理论、新方法和新讨论
- 多线程编程(15) - 多线程同步之 WaitableTimer (等待定时器对象)[续]
- 中继承父类实现父类方法的快捷键_关于封装、继承
- Linux redis配置服务,Linux 安装 redis 并配置成 service 系统服务
- html显示当前时间_HTML基础教程:超链接的使用
- 【nosql】NoSql是什么?
- 2016年程序员如何提高自己的方法有哪些?
- 小蚂蚁学习mysql性能优化(8)--数据库结构优化--范式化和反范式化,水平分表,垂直分表...
- cisco交换机命令大全(10)
- ffmpeg(5):SDL相关学习
- table标签中 如何固定td 宽度
- SpringBoot Maven repackage failed: Unable to find a single main class from the following candidates
- stm32通讯协议编写源码_STM32连接TFT-LCD
- RF无线射频电路设计干货分享
- 网易云易盾正式成为“中国短视频与直播联盟”常务理事
- 浙江高职考计算机专业本科,浙江高职自主招生考什么科目
- 海森堡量子力学与计算机,量子力学之父海森堡的大学生涯
- 上传到服务器的网站打开是空白,网站上传服务器,首页打开空白的解决办法|74cms|骑士cms...