python python可以做app吗内存管理吗

用Python的 __slots__ 节省9G内存 - 文章 - 伯乐在线
& 用Python的 __slots__ 节省9G内存
我们曾经提到,的Python web服务器怎样利用一个巨大的Python dicts(hash table),缓存大量的静态资源。我们最近在Image类中,用仅仅一行代码,让每个6G内存占用的服务进程(共4个),省出超过2G来。
这是其中一个服务器在部署代码前后的截图:
我们alloc了大约一百万个类似如下class的实例:
class Image(object):
def __init__(self, id, caption, url):
self.id = id
self.caption = caption
self.url = url
self._setup()
# ... other methods ...
class Image(object):&&&&def __init__(self, id, caption, url):&&&&&&&&self.id = id&&&&&&&&self.caption = caption&&&&&&&&self.url = url&&&&&&&&self._setup()&&&&&# ... other methods ...
默认情况下,Python用一个dict来存储对象实例的属性。这在一般情况下还不错,而且非常灵活,乃至你在运行时可以随意设置新的属性。
但是,对一些在”编译”前就知道该有几个固定属性的小class来说,这个dict就有点浪费内存了。而当你把这个小浪费乘上一百万,那可就大不同了。在Python中,你可以在class中设置__slots__,它是一个包含这些固定的属性名的list。这样Python就不会再使用dict,而且只分配这些属性的空间。
class Image(object):
__slots__ = ['id', 'caption', 'url']
def __init__(self, id, caption, url):
self.id = id
self.caption = caption
self.url = url
self._setup()
# ... other methods ...
12345678910
class Image(object):&&&&__slots__ = ['id', 'caption', 'url']&&&&&def __init__(self, id, caption, url):&&&&&&&&self.id = id&&&&&&&&self.caption = caption&&&&&&&&self.url = url&&&&&&&&self._setup()&&&&&# ... other methods ...
你还可以用collections.namedtuple,它允许访问参数,但只占用一个tuple的空间。这跟__slots__类似。不过我总觉得继承一个namedtuple类很奇怪。另外,如果你需要自定义初始化,你应该重载__new__而不是__init__。
警告:不要贸然进行这个优化,把它用在所有地方。这种做法不利于代码维护,而且只有当你有数以千计的实例的时候才会有明显效果。
译注:作者在评论中关于”不利于代码维护“的说法:
webreac:我觉得__slots__关键字不只是速度优化(注:这里应该是内存优化),也是类字段名的一个可靠”文档“。这有利于代码维护。为什么你觉得它不好?
Ben Hoyt(作者):有趣的说法——我不确定应不应该把__slots__作为文档。不过的确是不错的注意。我之前这么说的原因是,你需要对字段名”定义“两次(不够DRY)。namedtuple也类似。
关于作者:
可能感兴趣的话题
想问下这些技巧/知识点是在哪看到的?有没有较好的书籍推荐?基本语法还可以,但是这些技巧基本没接触过,想找本书看看
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线请看下面的一段代码:
origin = {'a':100,'b':[1,2,34,5]}
obj_copy ={};
obj_copy['key1']=
obj_copy['key2']=
print(obj_copy)
print('我们试图改变obj_copy中某个Key值的内容')
obj_copy['key1']['a'] = 10000
print(obj_copy)
obj_copy['key1']['b'] = "hello"
print(obj_copy)
print('----------------------')
obj_copy['key1']={'a':100,'b':[1,3,4,56,3]}
print(obj_copy)
#输出结果发生了改变
讲一下这段代码的意思:
我们首先给定一个字典origin = {'a':100,'b':[1,2,34,5]}
我们想要得到这个字典对象的一份拷贝,目的是在操作这个对象的时候不会更改该对象的属性。由于Python对象的引用机制,我们知道,当把一个对象赋给一个变量的时候,实际上是建立了一个该变量到对象的引用。如图和代码所示,这是最基本的Python内存管理机制。
&图片来自于网络。
&因此我们得到上段代码的输出结果:
{'a': 100, 'b': [1, 2, 34, 5]}
{'key2': {'a': 100, 'b': [1, 2, 34, 5]}, 'key1': {'a': 100, 'b': [1, 2, 34, 5]}}
我们试图改变obj_copy中某个Key值的内容
{'key2': {'a': 10000, 'b': [1, 2, 34, 5]}, 'key1': {'a': 10000, 'b': [1, 2, 34, 5]}}
{'key2': {'a': 10000, 'b': 'hello'}, 'key1': {'a': 10000, 'b': 'hello'}}
----------------------
{'key2': {'a': 10000, 'b': 'hello'}, 'key1': {'a': 100, 'b': [1, 3, 4, 56, 3]}}
其实这种改变在Javascript中也是一样的
obj.a = [21,2,3,4,5,67,8]
obj.b = {'key1':10,'key2':20,'key3':"hello,world"}
globalValue={};
globalValue.value1 =
globalValue.value2 =
globalValue.value1=[1,2,34,5,78]
alert('test')
我们同样可以通过这种方法实现。
在读这篇文章之前,可以先阅读一下的关于内存管理基础知识(虽然里面可能是由于版本问题,有点小错误)
阅读(...) 评论()Python 内存管理方式和垃圾回收算法 - Python - 伯乐在线
& Python 内存管理方式和垃圾回收算法
在列表,元组,实例,类,字典和函数中存在循环引用问题。有 __del__ 方法的实例会以健全的方式被处理。给新类型添加GC支持是很容易的。支持GC的Python与常规的Python是二进制兼容的。
分代式回收能运行工作(目前是三个分代)。由 pybench 实测的结果是大约有百分之四的开销。实际上所有的扩展模块都应该依然如故地正常工作(我不得不修改了标准发行版中的 new 和 cPickle 模块)。一个叫做 gc 的新模块马上就可以用来调试回收器和设置调试选项。
回收器应该是跨平台可移植的。Python 的补丁版本通过了所有的回归测试并且跑 Grail、Idle 和 Sketch 的时候没有任何问题。
自 Python 2.0 和之后的版本,可移植的垃圾回收机制已经包括在其中了。垃圾回收默认是开启的。请高兴些吧!
为什么我们需要垃圾回收?
目前版本的 Python 采用引用计数的方式来管理分配的内存。Python 的每个对象都有一个引用计数,这个引用计数表明了有多少对象在指向它。当这个引用计数为 0 时,该对象就释放了。引用计数对于多数程序都工作地很好。然而,引用计数有一个本质上的缺陷,是由于循环引用引起的。循环引用最简单的例子就是一个引用自身的对象。比如:
&&& l = []
&&& l.append(l)
&&& l = []&&& l.append(l)&&& del l
这个创建的列表的引用计数现在是 1。然而,因为它从 Python 内部已经无法访问,并且可能没法再被用到了,它应该被当作垃圾。在目前版本的 Python 中,这个列表永远不会被释放。
一般情况下循环引用不是一个好的编程实践,并且几乎总该被避免。然而,有时候很难避免制造循环引用,要么则是程序员甚至没有察觉到循环引用的问题。对于长期运行的程序,比如服务器,这个问题特别令人烦恼。人们可不想他们的服务器因为循环引用无法释放访问不到的对象而耗尽内存。对于大型程序,很难发现循环引用是怎么创造出来的。
“传统的”垃圾回收是怎样的?
传统的垃圾回收(比如标记-清除法或者停止-拷贝法)通常工作如下:
找到系统的根对象。根对象就像是全局的环境(比如 Python 中的 __main__ 模块)和堆栈上的对象。
从这些对象搜索所有的可以访问的对象。这些对象都是“活跃”的。
释放其他所有对象。
不幸的是这个方法不能用于当前版本的 Python。由于扩展模块的工作方式,Python 不能完全地确定根对象集合。如果根对象集合没法被准确地确定,我们就有释放仍然被引用的对象的风险。即使用其他方式设计扩展模块,也没有可移植的方式来找到当前 C 堆栈上的对象。而且,引用计数提供了一些 Python 程序员已然期待的有关局部性内存引用和终结语义的好处。最好是我们能够找到一个即能使用引用计数,又能够释放循环引用的的办法。
这个方法如何工作?
从概念上讲,这个方法与传统垃圾回收机制相反。这个方法试图去找到所有的不可访问对象,而不是去找所有的可访问对象。这样更加安全,因为如果这个算法失败了,起码不会比不进行垃圾回收还要糟(不考虑我们浪费掉的时间和空间)。
因为我们仍然在用引用计数,垃圾回收器只需要找到循环引用。引用计数会处理其他类型垃圾。首先我们观察到循环引用只能被容器对象创造。容器对象是可以包含其他对象的引用的对象。在Python中,列表、字典、实例、类和元祖都是容器对象的例子。整数和字符串不是容器。通过这个发现,我们意识到非容器对象可以被垃圾回收忽略。这是一个有用的优化因为整数和字符串这样的应该比较轻快。
现在我们的想法是记录所有的容器对象。有几种方法可以做到,然而最好的一种办法是利用双向链表,链表中的对象结构中包含指针字段。这样就可以使对象从集合中快速插入删除,而且不需要额外内存空间分配。当一个容器被创建,它就插入这个集合,被删除时,就从集合中去除。
既然我们能够得到所有的容器对象,我们怎么找到循环引用呢?首先我们往容器对象中添加两个指针外的另一个字段。我们命名这个字段 gc_refs。通过以下几步我们可以找到循环引用:
对每个容器对象,设 gc_refs 的值为对象的引用计数。
对每个容器对象,找到它引用的其他容器对象并把它们的 gc_refs 值减一。
所有的 gc_refs 大于 1 的容器对象是被容器对象集合外的对象所引用的。我们不能释放这些对象,所以我们把这些对象放到另一个集合。
被移走的对象所引用的对象也不能被释放。我们把它们和它们能访问到的对象都从目前集合移走。
在目前集合中的剩下的对象是仅被该集合中对象引用的(也就是说,他们无法被 Python 取到,也就是垃圾)。我们现在可以去释放这些对象。
Finalizer的问题
我们的宏伟计划还有一个问题,就是使用 finalizer 的问题。Finalizer 就是在 Python 中实例的__del__方法。使用引用计数时,Finalizer 工作地不错。当一个对象的引用计数降到 0 的时候,Finalizer 就在对象被释放前调用了。对程序员来说这是直接明了且容易理解的。
垃圾回收的时候,调用 finalizer 就成了一个麻烦的问题,尤其是面对循环引用的问题时。如果在循环引用中的两个对象都有 finalizer,该怎么做?先调用哪个?在调用第一个 finalizer 之后,这个对象无法被释放因为第二个 finalizer 还能取到它。
因为这个问题没有好的解决办法,被有 finalizer 的对象引用的循环是无法释放的。相反的,这些对象被加进一个全局的无法回收垃圾列表中。程序应该总是可以重新编写来避免这个问题。作为最后的手段,程序可以读取这个全局列表并以一种对于当前应用有意义的方式释放这些引用循环。
代价是什么?
就像有些人说的,天底下没有免费的午餐。然而,这种垃圾回收形式是相当廉价的。最大的代价之一是每各容器对象额外需要的三个字的内存空间。还有维护容器集合的开销。对当前版本的垃圾收集器来说,基于
这个开销大概是速度下降百分之四。
垃圾回收器目前记录对象的三代信息。通过调整参数,垃圾回收花费的时间可以想多小就多小。对一些应用来说,关掉自动垃圾回收并在运行时显式调用也许是有意义的。然而,以默认的垃圾回收参数运行 pybench,垃圾回收花费的时间看起来并不大。显而易见,大量分配容器对象的应用会引起更多的垃圾回收时间。
目前的补丁增加了一个新的配置项来激活垃圾回收器。有垃圾回收器的 Python 与标准 Python 是二进制兼容的。如果这个选项是关闭的,对 Python 解释器的工作就没有影响。
我该怎么使用它?
只要下载目前版本的 Python 就可以了。垃圾回收器已经包括在了 2.0 以后的版本中,并且默认是默认开启的。如果你在用 Python 1.5.2 版,这里有一个也许能工作的老版本的。如果你用的是 Windows 平台,你可以下载一个用来替代的 。
Boehm-Demers 保守垃圾回收
这个增加了一些修改到 Python 1.5.2,以使用 Boehm-Demers 保守垃圾回收。但是你必须先打上这个。依然是采用了引用计数。垃圾回收器只释放引用计数没有释放的内存(即循环引用)。这样应该性能最好。你需要:
$ cd Python-1.5.2
$ patch -p1 & ../gc-malloc-cleanup.diff
$ patch -p1 & ../gc-boehm.diff
$ autoconf
$ ./configure --with-gc
$ cd Python-1.5.2$ patch -p1 & ../gc-malloc-cleanup.diff$ patch -p1 & ../gc-boehm.diff$ autoconf$ ./configure --with-gc
这个补丁假设你安装了 libgc.a,使得 -lgc 链接选项可用(/usr/local/lib 也应该可以)。如果你没有这个库,在编译以前安装。
目前,这个补丁只在 Linux 上测试过。在其 他Unix 机器上也许也会工作。在我的 Linux 机器上,GC 版本的 Python 通过了所有的回归测试。
如果你试图修复一个内存泄漏的 Python 程序,Tim Perter 的 Cyclops 模块也许也有用。在
站上能找到。
感谢 ,这里有一个本页面的日文翻译版本。
更新于: 日,星期三 10:36:38 -0800
关于作者:
可能感兴趣的话题
2000年的文章,经过这么多版本的迭代,内存管理机制应该也有很多改变吧?
o 256 回复
关于 Python 频道
Python频道分享 Python 开发技术、相关的行业动态。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线python的内存管理机制,内存池相关的问题? - 知乎9被浏览702分享邀请回答0添加评论分享收藏感谢收起[Python]Python是如何进行内存管理的?
Python的内存管理是由Python得解释器负责的,开发人员可以从内存管理事务中解放出来,致力于应用程序的开发,这样就使得开发的程序错误更少,程序更健壮,开发周期更短.
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。}

我要回帖

更多关于 python内存管理 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信