详解如何减少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 相关文章推荐
Windows安装Python、pip、easy_install的方法
Mar 05 Python
使用Turtle画正螺旋线的方法
Sep 22 Python
python logging重复记录日志问题的解决方法
Jul 12 Python
使用Python 正则匹配两个特定字符之间的字符方法
Dec 24 Python
Python Web框架之Django框架Model基础详解
Aug 16 Python
python函数的作用域及关键字详解
Aug 20 Python
PyTorch中常用的激活函数的方法示例
Aug 20 Python
Django框架HttpRequest对象用法实例分析
Nov 01 Python
基于Django实现日志记录报错信息
Dec 17 Python
python pymysql链接数据库查询结果转为Dataframe实例
Jun 05 Python
Matplotlib 折线图plot()所有用法详解
Jul 28 Python
Python使用scapy模块发包收包
May 07 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实现图片等比例放大和缩小的方法详解
2013/06/06 PHP
PHP简单获取及判断提交来源的方法
2016/04/22 PHP
PHP7.1新功能之Nullable Type用法分析
2016/09/26 PHP
PHP实现上传多图即时显示与即时删除的方法
2017/05/09 PHP
asp.net下利用js实现返回上一页的实现方法小集
2009/11/24 Javascript
JavaScript EasyPager 分页函数
2011/05/25 Javascript
读jQuery之八 包装事件对象
2011/06/21 Javascript
js调试系列 断点与动态调试[基础篇]
2014/06/18 Javascript
JS中完美兼容各大浏览器的scrolltop方法
2015/04/17 Javascript
原生JS实现圆环拖拽效果
2017/04/07 Javascript
微信小程序实现图片轮播及文件上传
2017/04/07 Javascript
AngularJS实现tab选项卡的方法详解
2017/07/05 Javascript
ECMAScript6变量的解构赋值实例详解
2017/09/19 Javascript
详解webpack4升级指南以及从webpack3.x迁移
2018/06/12 Javascript
Vue项目数据动态过滤实践及实现思路
2018/09/11 Javascript
Ajax获取node服务器数据的完整步骤
2020/09/20 Javascript
原生js实现俄罗斯方块
2020/10/20 Javascript
Python中的列表知识点汇总
2015/04/14 Python
Django中更新多个对象数据与删除对象的方法
2015/07/17 Python
Python编程实现数学运算求一元二次方程的实根算法示例
2017/04/02 Python
简单谈谈python基本数据类型
2018/09/26 Python
python opencv 批量改变图片的尺寸大小的方法
2019/06/28 Python
Python3通过chmod修改目录或文件权限的方法示例
2020/06/08 Python
CSS3实现的渐变幻灯片效果
2020/12/07 HTML / CSS
HTML5 canvas 基本语法
2009/08/26 HTML / CSS
AmazeUI 等分网格的实现示例
2020/08/25 HTML / CSS
喜诗官方在线巧克力店:See’s Candies
2017/01/01 全球购物
巴西独家产品和现场演示购物网站:Shoptime
2019/07/11 全球购物
编写strcpy函数
2014/06/24 面试题
综合素质的自我鉴定
2013/10/07 职场文书
二年级语文教学反思
2014/02/02 职场文书
圣诞节红领巾广播稿
2014/02/03 职场文书
安全生产实施方案
2014/02/23 职场文书
车队司机自我鉴定
2014/03/02 职场文书
办公室年度工作总结2015
2015/05/21 职场文书
Python控制台输出俄罗斯方块移动和旋转功能
2021/04/18 Python