当程序执行过程中RAM中有大量对象处于活动状态时,可能会出现内存问题,特别是在对可用内存总量有限制的情况下。

下面概述了一些减小对象大小的方法,这些方法可以显著减少纯Python程序所需的RAM数量。

注: 这是我原帖子的英文版本(原帖子是用俄文写的)。(https://habr.com/ru/post/455722/ )

为了简单起见,我们将考虑用Python中的结构来表示坐标为x、y、z的点,并通过名称来访问坐标值。

Dict

在小程序中,特别是在脚本中,使用内置的dict来表示结构信息是非常简单方便的:

随着Python 3.6中使用一组有序键的更紧凑实现方式的出现,dict变得更有吸引力。但是,让我们看看它在RAM中的内存大小:

它需要大量内存,特别是当你突然需要创建大量实例时:

类实例

对于那些喜欢将所有东西放置在类中的人来说,最好将结构定义为一个可以通过属性名访问的类,:

类实例的结构很有趣:

这里的__weakref__是对这个对象的所谓弱引用列表的一个引用,__dict__字段是对类实例字典的引用,它包含实例属性的值(注意64位的引用平台会占用8个字节)。从Python 3.3开始,共享空间用于在字典中存储类的所有实例的键。这减少了RAM中实例堆栈的大小:

因此,大量的类实例占用的内存比一个普通字典(dict)占用的要小:

很容易看出,由于实例字典的大小,RAM中实例的大小仍然很大。

带有__slots__的类实例

通过消除 __dict__和__weakref__,可以显著减小RAM中的类实例的大小。这通过一个带有__slots__的小“技巧”是可能实现的:

RAM中的对象大小明显变小了:

在类定义中使用__slots__可以显著减少大量实例对内存空间的占用:

目前,这是大幅度减少RAM中类实例的内存占用的主要方法。

这是因为在内存中,对象引用会紧跟标题之后被存储在内存中——属性值,并通过类字典中的特殊描述符来访问它们:

要自动化使用 __slots__创建一个类的过程,有一个库[namedlist] (https://pypi.org/project/namedlist )可以使用。namedlist.namedlist函数会创建一个带有__slots__的类:

另一个包[attrs] (https://pypi.org/project/attrs )允许你使用和不使用__slots__自动创建类。

元组

Python还有一个内置的类型tuple(元组),用于表示不可变的数据结构。一个元组是一个固定的结构或记录,但没有字段名。对于字段访问,使用的是字段索引。元组字段在元组实例创建时就一次性与值对象相关联:

元组的实例是相当简洁的:

它们在内存中占用的字节比使用__slots__的类实例要多8个字节,因为内存中的元组跟踪也包含许多字段:

Namedtuple(命名元组)

由于元组使用的非常广泛,某天有人可能会提交一个通过名称访问字段的请求。这个请求的答案是collections.namedtuple模块。

namedtuple函数的目的是自动生成这样的类:

它会创建一个元组子类,其中定义了用于按名称访问字段的描述符。在我们的例子中,它看起来是这样的:

这些类的所有实例都具有与元组相同的内存占用量。大量的实例会占用更大的内存空间:

Recordclass: 没有循环GC的可变namedtuple

由于tuple和相应的namedtuple类会生成不可变对象,因此,ob.x属性就不能再与另一个值对象相关联了,对可变namedtuple变体的请求已经出现了。由于Python中没有与支持赋值的元组相同的内置类型,因此,开发者们创建了许多选项。我们将关注[recordclass] (https://pypi.org/project/recordclass ),它的评级为[stackoverflow] (https://stackoverflow.com/questions/29290359/ exists -of-mutable-name - tuplein -python / 29419745 )。此外,与类元组对象的大小相比,它还可以用来减小RAM中对象的大小。

包recordclass引入了recordclass.mutabletuple类型,它几乎与tuple相同,但它支持赋值。在此基础上,创建的子类几乎与namedtuple完全相同,但它支持将新值赋给字段(不需要创建新的实例)。recordclass函数与namedtuple函数一样,允许你自动创建这些类:

只有在没有PyGC_Head的情况下,类实例才具有与tuple相同的结构:

默认情况下,recordclass函数会创建一个不参与循环垃圾回收机制的类。通常,namedtuple和recordclass用于生成表示记录或简单(非递归)数据结构的类。在Python中正确使用它们就不会生成循环引用。出于这个原因, 在recordclass生成的类实例后面 ,默认情况下,PyGC_Head 部分会被排除在外, 这对支持循环垃圾回收机制(更准确地说:在与创建的类相关联的PyTypeObject结构中,默认情况下,flag字段中的Py_TPFLAGS_HAVE_GC是没有设置的)的类来说是必要的。

大量实例的内存占用量比使用了__slots__的类的实例要小:

Dataobject

recordclass库中提出的另一个解决方案是基于这样的思想:在内存中使用与带有__slots__的类实例相同的存储结构,但不参与循环垃圾回收机制。这些类是使用recordclass.make_dataclass数生成的:

默认情况下,以这种方式创建的类将创建可变实例。

另一种方法——使用继承自recordclass.dataobject的类声明:

以这种方式创建的类将创建不参与循环垃圾回收机制的实例。内存中实例的结构与使用__slots__的情况相同,但是没有PyGC_Head:

为了访问字段,还可以使用特殊的描述符通过它从对象开始的偏移量来访问,这些偏移量位于类字典中:

大量实例内存占用量的大小在CPython中可能是最小的:

Cython

还有一种基于使用[Cython] (https://cython.org )的方法。它的优点是字段可以接受C语言原子类型的值。自动创建用于从纯Python中来访问字段的描述符。例如:

在这种情况下,实例的内存占用更小:

内存中的实例跟踪的结构如下:

大量副本的占用空间要小一些:

但是,请记住,当你从Python代码访问时,每次都会执行从int到Python对象的转换,反之亦然。

Numpy

对大量数据使用多维数组或记录数组会增加内存占用。但是,为了在纯Python中进行有效的处理,你应该使用那些主要使用了numpy包中的函数的处理方法。

使用函数创建一个由N个元素组成的数组,并将其初始化为0:

内存中数组的大小是可能的最小值:

正常访问数组元素和行需要将Python对象转换为C中的 int值,反之亦然。提取单个行会创建一个包含单个元素的数组。它的追踪就不再那么简单了:

因此,如上所述,在Python代码中,有必要使用numpy包中的函数来处理数组。

结论

通过一个清晰而简单的示例,可以验证由开发人员和用户组成的Python编程语言(CPython)社区确实有可能显著减少对象使用的内存量。

英文原文:https://habr.com/en/post/458518/

译者:Nothing

python使用函数可以减少内存吗_如何将Python内存占用缩小20倍?相关推荐

  1. python减小内存占用_如何将Python内存占用缩小20倍?

    当程序执行过程中RAM中有大量对象处于活动状态时,可能会出现内存问题,特别是在对可用内存总量有限制的情况下. 下面概述了一些减小对象大小的方法,这些方法可以显著减少纯Python程序所需的RAM数量. ...

  2. python初级课程潭州百度云_潭州python 04基础班,资源教程下载

    课程名称 潭州python 04基础班,资源教程下载 课程介绍 潭州python 04基础班,零基础开始学习Python基础语法以及Python在各个领域的应用,如Python web开发.运维开发. ...

  3. python def函数报错详解_【python】详解python函数定义 def()与参数args、可变参数*args、关键参数**args使用实例...

    Python内置了很多函数,可以直接调用.Python内置的函数可以通过官方文档查看.也可以通过help()查看帮助信息.函数名是指向函数对象的引用,把函数名赋给变量,相当于给函数起了别名. 1. 定 ...

  4. python程序占用内存高_如何优化Python占用的内存,面试必学

    如果程序处理的数据比较多.比较复杂,那么在程序运行的时候,会占用大量的内存,当内存占用到达一定的数值,程序就有可能被操作系统终止,特别是在限制程序所使用的内存大小的场景,更容易发生问题.下面我就给出几 ...

  5. python函数的使用方法图解_零基础python之4函数重用-函数与模块(附详细的步骤和程序)...

    4代码重用--函数与模块 重用代码是构建一个可维护系统的关键. 代码组是Python中对块的叫法. 对之前的vowels代码功能创建一个函数名为search_for_vowels( ) 在函数调用时结 ...

  6. python for和while的用法区别_详解python while 函数及while和for的区别

    1.while循环(只有在条件表达式成立的时候才会进入while循环) while 条件表达式: pass while 条件表达式: pass else: pass 不知道循环次数,但确定循环条件的时 ...

  7. python 读取内存二叉树_二叉树类python

    python中的树数据结构 线性数据中的典型顺序表和链表已经讲完: <顺序表数据结构在python中的应用> <python实现单向链表数据结构及其基本方法> <pyth ...

  8. jvm内存结构_浅谈JVM内存结构

    JVM 可以分为 5 个部分,分别是: 类加载器(Class Loader):加载字节码文件到内存. 运行时数据区(Runtime Data Area):JVM 核心内存空间结构模型. 执行引擎(Ex ...

  9. python中的常量可以修改吗_深入理解Python变量与常量

    变量是计算机内存中的一块区域,变量可以存储规定范围内的值,而且值可以改变.基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中.常量是一块只读的内存区域,常量一旦被初始化就不能被 ...

最新文章

  1. 分析 AIX 和 Linux 性能工具nmon
  2. php zend 安装,全方位讲解PHP zend安装技巧
  3. myeclipse安装svn插件
  4. 深入理解Spark 2.1 Core (八):Standalone模式容错及HA的原理与源码分析
  5. 302状态码_你见过 HTTP 哪些状态码?
  6. 【Java】格式化的文件I/O
  7. java两个对象赋值_一起学Java(二十六)----- 对象之间赋值
  8. python 类不实例化方法:@staticmethod或@classmethod
  9. https://www.usb.org/
  10. 三同轴连接器_电子元器件 连接器相关知识
  11. SCCM 2012安装部署三:客户端管理
  12. 如何在Android上使背景20%透明
  13. IOT设备的7大安全问题
  14. 人口普查中应该具备的计算机知识,人口普查知识问答
  15. c语言对fpga编程,利用C语言对FPGA计算解决方案进行编程方法介绍
  16. python控制灯开关_通过树莓派控制电灯开关
  17. 如何在阿里云上创建安全的远程工作空间
  18. 《红楼梦》的香气韵调
  19. NDK51_OpenGL:FBO
  20. OpenAI-2018年强化学习领域7大最新研究方向全盘点

热门文章

  1. Jayway JsonPath实例
  2. Spring Boot AutoConfiguration注解@ConditionalXXXX之前生今世
  3. Xcode执行Analyze静态分析
  4. 元宇宙大比拼:苹果Apple, Facebook,微软,英伟达,iwemeta
  5. public class UserServiceImpl extends ServiceImpl UserMapper, User implements UserService
  6. 北京迎“豪宅元年”:四环房价将迈入8万元时代
  7. 雷军:小米逐梦之旅的三大秘诀
  8. jvm性能调优实战 -55RPC调用引发的OOM故障
  9. Redis进阶-Redis安全相关操作
  10. 白话Elasticsearch15-深度探秘搜索技术之使用copy_to定制组合field解决cross-fields搜索弊端