详解如何减少python内存的消耗


Posted in Python onAugust 09, 2019

Python 打算删除大量涉及像C和C++语言那样的复杂内存管理。当对象离开范围,就会被自动垃圾收集器回收。然而,对于由 Python 开发的大型且长期运行的系统来说,内存管理是不容小觑的事情。

在这篇博客中,我将会分享关于减少 Python 内存消耗的方法和分析导致内存消耗/膨胀根源的问题。这些都是从实际操作中总结的经验,我们正在构建 Datos IO 的 RecoverX 分布式备份和恢复平台,这里主要要介绍的是在 Python(在 C++ ,Java 和 bash 中也有一些类似的组件) 中的开发。

Python 垃圾收集

Python解释器对正在使用的对象保持计数。当对象不再被引用指向的时候,垃圾收集器可以释放该对象,获取分配的内存。例如,如果你使用常规的Python(CPython, 不是JPython)时,Python的垃圾收集器将调用free()/delete() 。

实用工具

资源(resource)

resource 模块用来查看项目当前得的固有的)内存消耗(固有内存是项目实际使用的RAM),注意resource库只在linux系统下有效

>>> import resource
>>> resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
443

对象(objgraph)

objgraph 是一个实用模块,可以展示当前内存中存在的对象

来看看objgraph的简单用法:

import objgraph
import random
import inspect

class Foo(object):

  def __init__(self):
    self.val = None

  def __str__(self):
    return "foo - val: {0}".format(self.val)

def f():

  l = []

  for i in range(3):
    foo = Foo()
    l.append(foo)

  return l


def main():

  d = {}

  l = f()

  d['k'] = l

  print "list l has {0} objectsoftype Foo()".format(len(l))

pythontest1.py

输出:

list l has 10000 objectsoftype Foo()
dict 10423
Foo 10000 ————> Guiltyas charged!
tuple 3349
wrapper_descriptor 945
function 860
builtin_function_or_method 616
method_descriptor 338
weakref 199
member_descriptor 161
getset_descriptor 107

注意,我们在内存中还持有10,423个‘dict'的实例对象。

可视化objgraph依赖项

Objgraph有个不错的功能,可以显示Foo()对象在内存中存在的因素,即,显示谁持有对它的引用 (在这个例子中是list l)。

在RedHat/Centos上, 你可以使用sudo yum install graphviz*安装graphviz
在Ubunbu等系统上使用sudo apt-get install graphviz*安装graphviz

如需查看对象字典 d,请参考:

objgraph.show_refs(d, filename='sample-graph.png')

详解如何减少python内存的消耗

从内存使用角度来看,我们惊奇地发现——为什么对象没有释放?这是因为有人在持有对它的引用。

这个小片段展示了objgraph怎样提供相关信息:

objgraph.show_backrefs(random.choice(objgraph.by_type('Foo')), filename="foo_refs.png")

详解如何减少python内存的消耗

在这一案例中, 我们查看了Foo类型的随机对象。我们知道该特定对象被保存在内存中,因其引用链接在指定范围内。

有时,以上技巧能帮助我们理解,为什么当我们不再使用某对象时,Python垃圾回收器没有将垃圾回收。

难处理的是,有时候我们会发现Foo()占用了很多内存的类。这时我们可以用heapy()来回答以上问题。

Heapy

heapy 是一个实用的,用于调试内存消耗/泄漏的工具。通常,我将objgraph和heapy搭配使用:用 heapy 查看分配对象随时间增长的差异,heapy能够显示对象持有的最大内存等;用Objgraph找backref链(例如:前4节),尝试获取它们不能被释放的原因。

Heapy的典型用法是在不同地方的代码中调用一个函数,试图为内存使用量提供大量收集线索,找到可能会引发的问题:

from guppyimport hpy


def dump_heap(h, i):
  """
  @param h: Theheap (from hp = hpy(), h = hp.heap())
  @param i: Identifierstr
  """

  print "Dumpingstatsat: {0}".format(i)

  print 'Memoryusage: {0}(MB)'.format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024)

  print "Mostcommontypes:"
  objgraph.show_most_common_types()

  print "heapis:"
  print "{0}".format(h)

  by_refs = h.byrcs
  print "byreferences: {0}".format(by_refs)
  print "Morestatsfor topelement.."
  print "Byclodo (class or dict owner): {0}".format(by_refs[0].byclodo)
  print "Bysize: {0}".format(by_refs[0].bysize)
  print "Byid: {0}".format(by_refs[0].byid)

减少内存消耗小技巧

在这一部分,我会介绍一些自己发现的可减少内存消耗的小窍门.

Slots

当你有许多对象时候可以使用Slots。Slotting传达给Python解释器:你的对象不需要动态的字典(从上面的例子2.2中,我们看到每个Foo()对象内部包含一个字典)

用slots定义你的对象,让python解释器知道你的类属性/成员是固定的.。这样可以有效地节约内存!

参考以下代码:

import resource

class Foo(object):
  #__slots__ = ('val1', 'val2', 'val3', 'val4', 'val5', 'val6')

  def __init__(self, val):
    self.val1 = val+1
    self.val2 = val+2
    self.val3 = val+3
    self.val4 = val+4
    self.val5 = val+5
    self.val6 = val+6

def f(count):
  l = []
  for i in range(count):
    foo = Foo(i)
    l.append(foo)

  return l

def main():
  count = 10000
  l = f(count)

  mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

  print "Memoryusageis: {0} KB”.format(mem)

  print "Sizeperfooobj: {0} KB”.format(float(mem)/count)

if __name__ == "__main__”:
  main()

[vagrant@datosdevtemp]$ pythontest2.py

输出:

Memoryusageis: 16672 KB
Sizeperfooobj: 1.6672 KB
Nowun-commentthisline: #__slots__ = (‘val1', ‘val2', ‘val3', ‘val4', ‘val5', ‘val6')
[vagrant@datosdevtemp]$ pythontest2.py
Memoryusageis: 6576 KB
Sizeperfooobj: 0.6576 KB

在这个例子中,减少了60%的内存消耗!

驻留:谨防驻留字符串!

Python会记录如字符串等不可改变的值(其每个值的大小依赖于实现方法),这称为驻留。

>>> t = "abcdefghijklmnopqrstuvwxyz"
>>>> p = "abcdefghijklmnopqrstuvwxyz"
>>>> id(t)
139863272322872
>>> id(p)
139863272322872

这是由python解析器完成的,这样做可以节省内存,并加快比较速度。例如,如果两个字符串拥有相同的ID或引用?他们就是全等的。

然而,如果你的程序创建了许多小的字符串,你的内存就会出现膨胀。

生成字符串时使用Format来代替“+”

接下来,在构造字符串时,使用Format来代替“+”构建字符串。

亦即,

st = "{0}_{1}_{2}_{3}".format(a,b,c,d) # 对内存更好,不创建临时变量
st2 = a + '_' + b + '_' + c + '_' + d # 在每个"+"时创建一个临时str,这些都是驻留在内存中的。

在我们的系统中,当我们将某些字符串构造从“+”变为使用format时,内存会明显被节省。

关于系统级别

上面我们讨论的技巧可以帮助你找出系统内存消耗的问题。但是,随着时间的推移,python进程产生的内存消耗会持续增加。这似乎与以下问题有关:

  1. 为什么C中内存分配能够在Python内部起作用,这本质上是内存碎片导致的。因为,除非整个内存没有使用过,否则该分配过程不能调用‘free'方法。但需要注意的是,内存的使用不是根据你所创建和使用的对象来进行排列。
  2. 内存增加也和上面讨论的“Interning” 有关。

以我的经验来看,减少python中内存消耗的比例是可行的。在Datos IO中,我曾经针对指定的内存消耗进程实现过一个工作模块。对于序列化的工作单元,我们运行了一个工作进程。当工作进程完成后, 它会被移除了——这是返回系统全部内存的唯一可以有效方法 :)。好的内存管理允许增加分配内存的大小,即允许工作进程长时间运行。

总结

我归纳了一些减少python进程消耗内存的技巧,当我们在代码中寻找内存泄漏时,一种方法是通过使用Heapy找出哪些Obj占用了较多内存,然后通过使用Objgraph找出内存被释放的原因(除非你认为他们本应该被释放)。

总的来说,我觉得在python中寻找内存问题是一种修行。随着时间的积累,对于系统中的内存膨胀和泄漏问题,你能产生一种直觉判断,并能更快地解决它们。愿你在发现问题的过程中找到乐趣!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python使用内存zipfile对象在内存中打包文件示例
Apr 30 Python
python生成日历实例解析
Aug 21 Python
Python2中的raw_input() 与 input()
Jun 12 Python
深入理解Python中的super()方法
Nov 20 Python
Python pyinotify日志监控系统处理日志的方法
Mar 08 Python
利用python为运维人员写一个监控脚本
Mar 25 Python
Python3.6.x中内置函数总结及讲解
Feb 22 Python
python中类的输出或类的实例输出为这种形式的原因
Aug 12 Python
Django使用uwsgi部署时的配置以及django日志文件的处理方法
Aug 30 Python
python实现大战外星人小游戏实例代码
Dec 26 Python
python 正则表达式参数替换实例详解
Jan 17 Python
python正则表达式的懒惰匹配和贪婪匹配说明
Jul 13 Python
Django命名URL和反向解析URL实现解析
Aug 09 #Python
利用python list完成最简单的DB连接池方法
Aug 09 #Python
python使用requests.session模拟登录
Aug 09 #Python
如何在Cloud Studio上执行Python代码?
Aug 09 #Python
python切片(获取一个子列表(数组))详解
Aug 09 #Python
Python多叉树的构造及取出节点数据(treelib)的方法
Aug 09 #Python
一行python实现树形结构的方法
Aug 09 #Python
You might like
php curl常见错误:SSL错误、bool(false)
2011/12/28 PHP
php使用array_rand()函数从数组中随机选择一个或多个元素
2014/04/28 PHP
php中青蛙跳台阶的问题解决方法
2018/10/14 PHP
ASP.NET jQuery 实例14 在ASP.NET form中校验时间范围
2012/02/03 Javascript
JavaScript实现表格排序方法
2013/06/14 Javascript
详解JavaScript中的异常处理方法
2015/06/16 Javascript
js实现商城星星评分的效果
2015/12/29 Javascript
Three.js快速入门教程
2016/09/09 Javascript
Vue.js使用v-show和v-if的注意事项
2016/12/13 Javascript
AngularJS实现使用路由切换视图的方法
2017/01/24 Javascript
简单实现jQuery弹窗效果
2017/10/30 jQuery
Vue+axios实现统一接口管理的方法
2018/07/23 Javascript
浅谈vue中关于checkbox数据绑定v-model指令的个人理解
2018/11/14 Javascript
react 组件传值的三种方法
2019/06/03 Javascript
js实现简单的无缝轮播效果
2020/09/05 Javascript
[59:08]DOTA2上海特级锦标赛C组小组赛#2 LGD VS Newbee第一局
2016/02/27 DOTA
理解python多线程(python多线程简明教程)
2014/06/09 Python
使用 Python 获取 Linux 系统信息的代码
2014/07/13 Python
修复 Django migration 时遇到的问题解决
2018/06/14 Python
Python/Django后端使用PIL Image生成头像缩略图
2019/04/30 Python
python代码 FTP备份交换机配置脚本实例解析
2019/08/01 Python
python 接口实现 供第三方调用的例子
2019/08/13 Python
解决Python二维数组赋值问题
2019/11/28 Python
pytorch实现CNN卷积神经网络
2020/02/19 Python
Django框架models使用group by详解
2020/03/11 Python
python实现凯撒密码、凯撒加解密算法
2020/06/11 Python
Django启动时找不到mysqlclient问题解决方案
2020/11/11 Python
基于PyTorch中view的用法说明
2021/03/03 Python
美国用餐电影院:Alamo Drafthouse Cinema
2020/01/23 全球购物
师范生的个人求职信范文
2014/01/04 职场文书
幼儿园教师节活动方案
2014/02/02 职场文书
应届中专生自荐书范文
2014/02/13 职场文书
php微信小程序解包过程实例详解
2021/03/31 PHP
Linux系统下安装PHP7.3版本
2021/06/26 PHP
Nginx代理Redis哨兵主从配置的实现
2022/07/15 Servers
postgresql之greenplum字符串去重拼接方式
2023/05/08 PostgreSQL