扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
(1).引用计数
网站制作、网站建设的开发,更需要了解用户,从用户角度来建设网站,获得较好的用户体验。创新互联多年互联网经验,见的多,沟通容易、能帮助客户提出的运营建议。作为成都一家网络公司,打造的就是网站建设产品直销的概念。选择创新互联,不只是建站,我们把建站作为产品,不断的更新、完善,让每位来访用户感受到浩方产品的价值服务。
(2). 垃圾回收
(3). 内存池机制
在python中每创建一个对象,对应的会有一个引用计数,当发生赋值操作如a=b,对应的b的引用计数会自动加1,当引用的对象被清除或者函数结束时,引用计数会自动减1。
在python中使用引用计数,标记清楚,分代回收三种方式进行垃圾回收。
其中,引用计数当对象的引用计数归0时,对象会自动被清除。标记清除机制是首先遍历所有对象,如果对象可达,就说明有变量引用它,则标记其为可达的。如果不可达,则对其进行清除。分代回收是当对象创建时被标记为第0代,经过一次垃圾回收之后,余下的对象被标记为第1代,最高为第2代。其原理是,对象的生存期越长,月可能不是垃越。
ython语言虽然提供了对内存的垃圾收集机制,但实际上它将不用的内存放到内存池而不是返回给操作系统,所以就有了以下:
1 Pymalloc机制;这个主要是为了加速Python的执行效率,Python引入了一个内存池机制,用于管理,为了对小块内存的申请和释放。
2 Python中所有小于256个字节的对象都是依靠pymalloc分配器来实现的,而稍大的对象用的则是系统的malloc。
3 对于Python对象,比如整数、浮点数和List这些,都有自己独立的内存池,对象间并不共享他们的内存池。换句话说就是,假设你分配并且释放了大量的整数,那么用于缓存这些整数的内存就不能再分配给浮点数。
由于python中万物皆对象,所以python的存储问题是对象的存储问题。实际上,对于每个对象,python会分配一块内存空间去存储它。
那么python是如何进行内存分配,如何进行内存管理,又是如何释放内存的呢?
总结起来有一下几个方面:引用计数,垃圾回收,内存池机制
python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数
1、对象被创建 a= 'abc'
2、对象被引用 b =a
3、对象被其他的对象引用 li = [1,2,a]
4、对象被作为参数传递给函数:foo(x)
1、变量被删除 del a 或者 del b
2、变量引用了其他对象 b = c 或者 a = c
3、变量离开了所在的作用域(函数调用结束) 比如上面的foo(x)函数结束时,x指向的对象引用减1。
4、在其他的引用对象中被删除(移除) li.remove(a)
5、窗口对象本身被销毁:del li,或者窗口对象本身离开了作用域。
即对象p中的属性引用d,而对象d中属性同时来引用p,从而造成仅仅删除p和d对象,也无法释放其内存空间,因为他们依然在被引用。深入解释就是,循环引用后,p和d被引用个数为2,删除p和d对象后,两者被引用个数变为1,并不是0,而python只有在检查到一个对象的被引用个数为0时,才会自动释放其内存,所以这里无法释放p和d的内存空间
垃圾回收机制: ① 引用计数 , ②标记清除 , ③分带回收
引用计数也是一种垃圾收集机制, 而且也是一种最直观, 最简单的垃圾收集技术.当python某个对象的引用计数降为 0 时, 说明没有任何引用指向该对象, 该对象就成为要被回收的垃圾了.(如果出现循环引用的话, 引用计数机制就不再起作用了)
优点:简单实时性,缺点:维护引用计数消耗资源,且无法解决循环引用。
如果两个对象的引用计数都为 1 , 但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的, 也就是说 它们的引用计数虽然表现为非 0 , 但实际上有效的引用计数为 0 ,.所以先将循环引用摘掉, 就会得出这两个对象的有效计数.
标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。
为了提高效率,有很多对象,清理了很多次他依然存在,可以认为,这样的对象不需要经常回收,可以把它分到不同的集合,每个集合回收的时间间隔不同。简单的说这就是python的分代回收。
具体来说,python中的垃圾分为1,2,3代,在1代里的对象每次回收都会去清理,当清理后有引用的对象依然存在,此时他会进入2代集合,同理2代集合清理的时候存在的对象会进入3代集合。
每个集合的清理时间如何分配:会先清理1代垃圾,当清理10次一代垃圾后会清理一次2代垃圾,当清理10次2代垃圾后会清理3代垃圾。
在Python中,许多时候申请的内存都是小块的内存,这些小块内存在申请后,很快又会被释放,当创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降低。
内存池的概念就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。
Python中有分为大内存和小内存:(256K为界限分大小内存)
大小小于256kb时,pymalloc会在内存池中申请内存空间,当大于256kb,则会直接执行 new/malloc 的行为来申请新的内存空间
在python中 -5到256之间的数据,系统会默认给每个数字分配一个内存区域,其后有赋值时都会指向固定的已分配的内存区域
在运行py程序的时候,解释器会专门分配一块空白的内存,用来存放纯单词字符组成的字符串(数字,字母,下划线)
字符串赋值时,会先去查找要赋值的字符串是否已存在于内存区域,已存在,则指向已存在的内存,不存在,则会在大整数池中分配一块内存存放此字符串
Dict
在小型程序中,特别是在脚本中,使用Python自带的dict来表示结构信息非常简单方便:
ob = {'x':1, 'y':2, 'z':3}
x = ob['x']
ob['y'] = y
由于在Python 3.6中dict的实现采用了一组有序键,因此其结构更为紧凑,更深得人心。但是,让我们看看dict在内容中占用的空间大小:
print(sys.getsizeof(ob))
240
如上所示,dict占用了大量内存,尤其是如果突然虚需要创建大量实例时:
实例数
对象大小
1 000 000
240 Mb
10 000 000
2.40 Gb
100 000 000
24 Gb
类实例
有些人希望将所有东西都封装到类中,他们更喜欢将结构定义为可以通过属性名访问的类:
class Point:
#
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
ob = Point(1,2,3)
x = ob.x
ob.y = y
类实例的结构很有趣:
字段
大小(比特)
PyGC_Head
24
PyObject_HEAD
16
__weakref__
8
__dict__
8
合计:
56
在上表中,__weakref__是该列表的引用,称之为到该对象的弱引用(weak reference);字段__dict__是该类的实例字典的引用,其中包含实例属性的值(注意在64-bit引用平台中占用8字节)。从Python3.3开始,所有类实例的字典的键都存储在共享空间中。这样就减少了内存中实例的大小:
print(sys.getsizeof(ob), sys.getsizeof(ob.__dict__))
56 112
因此,大量类实例在内存中占用的空间少于常规字典(dict):
实例数
大小
1 000 000
168 Mb
10 000 000
1.68 Gb
100 000 000
16.8 Gb
不难看出,由于实例的字典很大,所以实例依然占用了大量内存。
带有__slots__的类实例
为了大幅降低内存中类实例的大小,我们可以考虑干掉__dict__和__weakref__。为此,我们可以借助 __slots__:
class Point:
__slots__ = 'x', 'y', 'z'
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
ob = Point(1,2,3)
print(sys.getsizeof(ob))
64
如此一来,内存中的对象就明显变小了:
字段
大小(比特)
PyGC_Head
24
PyObject_HEAD
16
x
8
y
8
z
8
总计:
64
在类的定义中使用了__slots__以后,大量实例占据的内存就明显减少了:
实例数
大小
1 000 000
64 Mb
10 000 000
640 Mb
100 000 000
6.4 Gb
目前,这是降低类实例占用内存的主要方式。
这种方式减少内存的原理为:在内存中,对象的标题后面存储的是对象的引用(即属性值),访问这些属性值可以使用类字典中的特殊描述符:
pprint(Point.__dict__)
mappingproxy(
....................................
'x': ,
'y': ,
'z': })
为了自动化使用__slots__创建类的过程,你可以使用库namedlist()。namedlist.namedlist函数可以创建带有__slots__的类:
Point = namedlist('Point', ('x', 'y', 'z'))
还有一个包attrs(),无论使用或不使用__slots__都可以利用这个包自动创建类。
元组
Python还有一个自带的元组(tuple)类型,代表不可修改的数据结构。元组是固定的结构或记录,但它不包含字段名称。你可以利用字段索引访问元组的字段。在创建元组实例时,元组的字段会一次性关联到值对象:
ob = (1,2,3)
x = ob[0]
ob[1] = y # ERROR
元组实例非常紧凑:
print(sys.getsizeof(ob))
72
由于内存中的元组还包含字段数,因此需要占据内存的8个字节,多于带有__slots__的类:
字段
大小(字节)
PyGC_Head
24
PyObject_HEAD
16
ob_size
8
[0]
8
[1]
8
[2]
8
总计:
72
命名元组
由于元组的使用非常广泛,所以终有一天你需要通过名称访问元组。为了满足这种需求,你可以使用模块collections.namedtuple。
namedtuple函数可以自动生成这种类:
Point = namedtuple('Point', ('x', 'y', 'z'))
如上代码创建了元组的子类,其中还定义了通过名称访问字段的描述符。对于上述示例,访问方式如下:
class Point(tuple):
#
@property
def _get_x(self):
return self[0]
@property
def _get_y(self):
return self[1]
@property
def _get_z(self):
return self[2]
#
def __new__(cls, x, y, z):
return tuple.__new__(cls, (x, y, z))
这种类所有的实例所占用的内存与元组完全相同。但大量的实例占用的内存也会稍稍多一些:
实例数
大小
1 000 000
72 Mb
10 000 000
720 Mb
100 000 000
7.2 Gb
记录类:不带循环GC的可变更命名元组
由于元组及其相应的命名元组类能够生成不可修改的对象,因此类似于ob.x的对象值不能再被赋予其他值,所以有时还需要可修改的命名元组。由于Python没有相当于元组且支持赋值的内置类型,因此人们想了许多办法。在这里我们讨论一下记录类(recordclass,),它在StackoverFlow上广受好评()。
此外,它还可以将对象占用的内存量减少到与元组对象差不多的水平。
recordclass包引入了类型recordclass.mutabletuple,它几乎等价于元组,但它支持赋值。它会创建几乎与namedtuple完全一致的子类,但支持给属性赋新值(而不需要创建新的实例)。recordclass函数与namedtuple函数类似,可以自动创建这些类:
Point = recordclass('Point', ('x', 'y', 'z'))
ob = Point(1, 2, 3)
类实例的结构也类似于tuple,但没有PyGC_Head:
字段
大小(字节)
PyObject_HEAD
16
ob_size
8
x
8
y
8
z
8
总计:
48
在默认情况下,recordclass函数会创建一个类,该类不参与垃圾回收机制。一般来说,namedtuple和recordclass都可以生成表示记录或简单数据结构(即非递归结构)的类。在Python中正确使用这二者不会造成循环引用。因此,recordclass生成的类实例默认情况下不包含PyGC_Head片段(这个片段是支持循环垃圾回收机制的必需字段,或者更准确地说,在创建类的PyTypeObject结构中,flags字段默认情况下不会设置Py_TPFLAGS_HAVE_GC标志)。
大量实例占用的内存量要小于带有__slots__的类实例:
实例数
大小
1 000 000
48 Mb10 000 000
480 Mb
100 000 000
4.8 Gb
dataobject
recordclass库提出的另一个解决方案的基本想法为:内存结构采用与带__slots__的类实例同样的结构,但不参与循环垃圾回收机制。这种类可以通过recordclass.make_dataclass函数生成:
Point = make_dataclass('Point', ('x', 'y', 'z'))
这种方式创建的类默认会生成可修改的实例。
另一种方法是从recordclass.dataobject继承:
class Point(dataobject):
x:int
y:int
z:int
这种方法创建的类实例不会参与循环垃圾回收机制。内存中实例的结构与带有__slots__的类相同,但没有PyGC_Head:
字段
大小(字节)
PyObject_HEAD
16
ob_size
8
x
8
y
8
z
8
总计:
48
ob = Point(1,2,3)
print(sys.getsizeof(ob))
40
如果想访问字段,则需要使用特殊的描述符来表示从对象开头算起的偏移量,其位置位于类字典内:
mappingproxy({'__new__': ,
.......................................
'x': ,
'y': ,
'z': })
大量实例占用的内存量在CPython实现中是最小的:
实例数
大小
1 000 000
40 Mb
10 000 000
400 Mb
100 000 000
4.0 Gb
Cython
还有一个基于Cython()的方案。该方案的优点是字段可以使用C语言的原子类型。访问字段的描述符可以通过纯Python创建。例如:
cdef class Python:
cdef public int x, y, z
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
本例中实例占用的内存更小:
ob = Point(1,2,3)
print(sys.getsizeof(ob))
32
内存结构如下:
字段
大小(字节)
我觉得可能是因为你的py文件在第一次启动后,已经编译成pyc文件了,再次启动的时候都是加载pyc,省去了编译的阶段,所以速度很快。
你可以试着把程序目录下的所有pyc或者你的代码文件对应的pyc文件删除,看看是不是可以和第一次加载速度相同
一般情况下不会占内存,而且Python的内存需要的很小。如果用模拟器的话需要退出一下,输入close就可以了。请采纳,谢谢。
python 怎么在循环中释放内存
#include"stdio.h"
main()
{
char st[15];
printf("input string:\n");
gets(st);
puts(st);
}
可以看出当输入的字符串中含有空格时,输出仍为全部字符串。说明gets函数并不以空格作为字符串输入结束的标志,而只以回车作为输入结束。这是与scanf函数不同的。
3. 字符串连接函数strcat
格式: strcat (字符数组名1,字符数组名2)
功能:把字符数组2中的字符串连接到字符数组1 中字符串的后面,并删去字符串1后的串标志“\0”。本函数返回值是字符数组1的首地址。
【例7.14】
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流