当前位置: 首页 > 后端技术 > Python

Python内存分配小秘密

时间:2023-03-25 21:03:14 Python

文章来源:Python猫微信公众号作者:豌豆花猫Python中的sys模块极其基础和重要,主要提供一些供解释器使用(或由其维护)变量,以及一些与解释器有强烈交互的函数。本文会经常用到该模块的getsizeof()方法,先简单介绍一下:该方法用于获取对象的字节大小(bytes),只计算直接占用的内存,不计算all下面是一个引用对象内存的直观例子:importsysa=[1,2]b=[a,a]#即[[1,2],[1,2]]a和b只有两个元素,所以直接占用的大小是相等的。无论它的元素指向什么,它的内存都是80字节。好了,有了这个测量工具,我们就来探索一下Python内置对象到底隐藏了哪些小秘密。1.空对象不“空”!对于我们熟悉的一些空对象,比如空字符串、空列表、空字典等,不知道大家有没有想过,有没有想过这些问题:空对象不占内存吗?如果占用内存,占用多少?为什么会这样分配?直接上代码,先看一下几种基本数据结构的空对象大小:`importsyssys.getsizeof("")#49sys.getsizeof([])#64sys.getsizeof(())#48sys.getsizeof(set())#224sys.getsizeof(dict())#240作为参考:sys.getsizeof(1)#28sys.getsizeof(True)#28`可以看出,虽然都是空对象,这些对象在内存中没有分配不是“空”的,而且分配的是相当大的(记住这些数字,后面会测试)。排序方式:基数<空元组<空字符串<空列表<空集合<空字典。这个小秘密怎么解释呢?因为这些空对象都是容器,我们可以抽象地理解:它们的一部分内存用于创建容器的骨架,记录容器的信息(如引用计数、使用信息等),一部分用于内存是预先分配的。2.内存扩展不统一!空对象不是空的,部分原因是Python解释器为它们预分配了一些初始空间。在不超过初始内存的情况下,每增加一个元素,都会使用已有的内存,从而避免了申请新内存的情况。那么,如果分配了初始内存,那么新的内存又是如何分配的呢?`importsysletters="abcdefghijklmnopqrstuvwxyz"a=[]foriinletters:a.append(i)print(f'{len(a)},sys.getsizeof(a)={sys.getsizeof(a)}')b=set()forjinletters:b.add(j)print(f'{len(b)},sys.getsizeof(b)={sys.getsizeof(b)}')c=dict()forkinletters:c[k]=kprint(f'{len(c)},sys.getsizeof(c)={sys.getsizeof(c)}')`三类变量对象添加26个元素,看结果:由此可见可变对象扩容的秘密:过度分配机制:在申请新内存时,不是按需分配,而是多分配,所以在添加少量元素时,不需要立即申请新的内存非均匀分配机制:三类对象申请新内存的频率不同,同一类对象每次分配的内存不是统一的,而是逐渐扩大的。3.列表不等于列表!上面的变量对象在展开的时候也有类似的分配机制,动态展开的时候可以很明显的看到效果。那么,静态创建的对象是否也有这样的分配机制呢?它和动态扩展有什么区别吗?我们先看看集合和字典:`#静态创建对象set_1={1,2,3,4}set_2={1,2,3,4,5}dict_1={'a':1,'b':2,'c':3,'d':4,'e':5}dict_2={'a':1,'b':2,'c':3,'d':4,'e':5,'f':6}sys.getsizeof(set_1)#224sys.getsizeof(set_2)#736sys.getsizeof(dict_1)#240sys.getsizeof(dict_2)#368看到这个结果,再对比之前的截图section,可以看出当元素个数相等时,静态创建的集合/字典占用的内存和动态扩展时完全一样。这个结论适用于列表对象吗?看一看:`list_1=['a','b']list_2=['a','b','c']list_3=['a','b','c','d']list_4=['a','b','c','d','e']sys.getsizeof(list_1)#80sys.getsizeof(list_2)#88sys.getsizeof(list_3)#96sys.getsizeof(list_4)#104`上一节的截图显示,列表有前4个元素时占96字节,有5个元素时占128字节,这里明显矛盾。因此,其中的奥妙就显而易见了:当元素个数相等时,静态创建的列表占用的内存可能比动态扩展占用的内存小!也就是两个表看似相同,实则不同!列表不等于列表!4.减少元素不会释放内存!如前所述,在扩展可变对象时,可能会请求新的内存。那么,如果对可变对象进行反向reduce,在reduce一些元素后,新分配的内存会自动回收吗?`importsysa=[1,2,3,4]sys.getsizeof(a)#初始值:96a.append(5)#展开后:[1,2,3,4,5]sys.getsizeof(a)#展开后:128a.pop()#缩减后:[1,2,3,4]sys.getsizeof(a)#缩减后:128`如代码所示,列表扩缩后,虽然它返回原样,但占用的内存空间不会自动释放。其他可变对象也是如此。这就是Python的小秘密,《胖人瘦不下来的原理》:瘦的人很容易变胖,也很容易减体型,但是体重减不下来,哈哈~~~5.空字典不等于空字典!使用pop()方法只会减少变量对象中的元素,并不会释放分配的内存空间。还有一个clear()方法,它会清除可变对象的所有元素,让我们试试看:`importsysa=[1,2,3]b={1,2,3}c={'a':1,'b':2,'c':3}sys.getsizeof(a)#88sys.getsizeof(b)#224sys.getsizeof(c)#240a.clear()#清除后:[]b.clear()#清空后:set()c.clear()#清空后:{},即dict()`调用clear()方法,得到几个空对象。在第一部分中,检查了它们的内存大小。(我之前说过我要考了,请_写回去再读)然而这时候你再去查看,你会惊奇的发现这些空对象的大小和原来的不完全一样之前的检查!`#延续之前的清空操作:sys.getsizeof(a)#64sys.getsizeof(b)#224sys.getsizeof(c)#72`空列表和空元组的大小保持不变,但是空字典(72)其实比前面的空字典(240)小很多!也就是说,list和tuple清空元素后,不改变初衷,回到起点。我丢了钱!字典的秘密很深。说实话,我也是刚学,想不通。。。以上就是Python在分配内存时的几个小秘密。看完之后是不是觉得自己的知识又增长了呢?你想出了多少,又创造了多少新的谜团?欢迎大家留言一起交流~对于那些还没有完全解释清楚的小秘密,我们以后会慢慢揭秘的……