当前位置: 首页 > news >正文

Python的内存优化

在Python中,内存管理和优化是一个复杂的话题,因为它涉及到Python解释器的内部机制,特别是Python的垃圾收集和内存分配策略。Python通过自动垃圾收集机制管理内存,主要包括引用计数和标记-清除算法。

Python内存管理机制:

1. 引用计数
Python内部使用引用计数来跟踪对象被引用的次数。每当你创建了一个对象,其引用计数就会被设置为1,每当对象被另一个变量名引用,或者被添加到一个容器中(如列表、元组或字典等),其引用计数就会增加。当引用到该对象的变量被删除,或者引用被赋予新的对象时,引用计数就会减少。当对象的引用计数降到0时,内存就会被释放。

2. 垃圾收集
引用计数无法解决循环引用的问题(两个或多个对象相互引用,但不再被别的对象引用)。为了解决这个问题,Python有一个垃圾收集器,它能够跟踪这些循环引用并且删除它们。
Python的垃圾收集器采用分代收集的算法,它将对象分配到三个不同的“代”中。新创建的对象会被放入第一代中,如果它们在一次垃圾收集之后仍然存活,那么它们就会被移到第二代,以此类推。这个方法基于这样一个观察:存活时间较长的对象很可能会存活得更久。

3. 内存池
Python使用一个内存池内存管理机制来避免为每个小对象都进行系统调用。这个机制叫做“分区”,它专门处理小于256字节的对象。这种方法通过减少系统调用次数来提高效率,因为请求和释放内存是一个相对昂贵的操作。

优化策略:

1. 使用内建容器:

Python的内建容器,如列表、元组和字典,经过高度优化,通常比自定义数据结构更节省内存。

2. 数据结构选择:

根据数据量和操作类型合理选择数据结构。例如,对于只包含数值的密集型数据集,使用array.arraynumpy数组通常比使用列表更高效。

3. 对象池:

对于频繁创建和销毁的小对象,使用对象池可以避免不断地进行内存分配和回收。
对象池(Object Pool)是一种设计模式,用于管理对象缓存的创建和回收。这种模式的目的是通过重用已经创建的对象来避免频繁地创建和销毁对象,从而减少程序运行中的内存分配和回收开销,提高性能。

如何实现对象池

一个简单的对象池可以使用队列或栈数据结构来实现。以下是一个简单的Python对象池实现示例:

import queueclass ObjectPool:def __init__(self, create_func, max_size):self._create_func = create_funcself._pool = queue.Queue(max_size)def get(self):try:return self._pool.get_nowait()except queue.Empty:return self._create_func()def put(self, item):try:self._pool.put_nowait(item)except queue.Full:# 如果队列已满,则忽略或者可以选择销毁对象pass# 假设有一个复杂对象的创建函数
def create_expensive_object():return SomeExpensiveObject()# 创建一个容量为10的对象池
pool = ObjectPool(create_expensive_object, 10)# 获取一个对象
obj = pool.get()# ... 使用对象 ...# 当完成使用后,将对象放回池中
pool.put(obj)

在上面的代码中,create_func 是一个函数,用于创建新的对象。对象池会尝试从队列中获取已有的对象,如果队列为空,它会创建一个新对象。使用完对象后,调用put()方法将对象返回池中。

4. 懒加载:

只有在需要时才加载数据可以减少内存的占用。

懒加载是一种设计模式和优化策略,通常用于推迟某个对象的创建、某个计算的执行或者某个过程的发生,直到真正需要它的时候。这可以显著减少程序的启动时间和运行时的内存占用,因为不是在一开始就加载所有可能需要的资源,而是在需要它们的确切时刻才去加载。

实现懒加载的技术
使用属性(Properties)
class LazyProperty:def __init__(self, method):self.method = methodself.method_name = method.__name__def __get__(self, obj, cls):if not obj:return Nonevalue = self.method(obj)setattr(obj, self.method_name, value)return valueclass MyClass:@LazyPropertydef expensive_to_compute(self):print("Computing value...")return sum(i * i for i in range(10000))obj = MyClass()
print(obj.expensive_to_compute)  # 计算并返回结果
print(obj.expensive_to_compute)  # 直接返回结果,不再计算

在上面的例子中,expensive_to_compute 方法的结果会在第一次访问时被计算并缓存,后续访问将直接返回缓存的值。

使用模块级别的延迟导入
# lazy_module.pydef expensive_import():from some_expensive_module import ExpensiveClassreturn ExpensiveClass()

在这个例子中,some_expensive_module 只会在 expensive_import 函数被调用时才会被导入,而不是在模块加载时。

使用生成器(Generators)
def read_large_file(file_name):"""懒加载大文件的行"""for line in open(file_name, "r"):yield line# 这样就可以一次读取一行,而不必一次将整个文件加载到内存中
for line in read_large_file("large_file.txt"):process(line)

懒加载在处理大数据集或资源密集型操作时尤其有用,因为它可以帮助避免不必要的内存消耗和计算开销。在实现懒加载时,开发者应该注意保证代码的清晰性和可维护性,并确保延迟加载的资源在使用时能够正确地加载和初始化。

5. 内存分析和剖析:

定期使用内存分析工具,如memory_profilertracemalloc,来查找和修复内存问题。

  1. memory_profiler:

    • 这是一个用于监视Python代码的内存使用情况的库。
    • 可以作为一个独立的程序来运行,或者作为一个装饰器添加到你的函数中。
    • 它提供了详细的行级内存使用报告。
      如何使用:
    from memory_profiler import profile@profile
    def my_func():a = [1] * (10**6)b = [2] * (2 * 10**7)del breturn aif __name__ == '__main__':my_func()
    

    运行此脚本将生成每行的内存使用报告。

  2. objgraph:

    • objgraph是一个用于显示Python程序中对象引用关系的库,可以帮助分析内存泄漏。
    • 它可以生成对象引用关系图,帮助可视化内存使用情况。

    如何使用:

    import objgraph
    x = [1]
    y = [x, [x], {'x': x}]
    objgraph.show_refs([y], filename='ref_graph.png')  # 创建一个图形文件,展示y的引用图。
    
  3. Pympler:

    • Pympler是一个用于分析Python内存使用情况的库,提供了一个方便的web界面。
    • 它可以跟踪内存使用情况,检测内存泄漏,并帮助开发者理解内存消耗。

    如何使用:

    from pympler import summary, muppy
    all_objects = muppy.get_objects()
    sum1 = summary.summarize(all_objects)
    summary.print_(sum1)
    
  4. tracemalloc:

    • tracemalloc是Python标准库的一部分,它可以跟踪内存分配。
    • 它可以告诉你在哪些行上分配了多少内存。

    如何使用:

    import tracemalloctracemalloc.start()# ... 执行代码 ...snapshot = tracemalloc.take_snapshot()
    top_stats = snapshot.statistics('lineno')print("[ Top 10 ]")
    for stat in top_stats[:10]:print(stat)
    

6. 减少引用:

减少不必要的对象引用,及时释放不再需要的对象。

7. 避免循环引用:

尽可能避免循环引用,或者使用弱引用weakref模块来处理它们。
在Python中,弱引用是一种不会增加对象引用计数的特殊引用。它允许一个对象被引用而不阻止它被垃圾回收器回收。这在缓存或映射中特别有用,因为它们可以存储对象但不妨碍对象的生命周期。

弱引用的使用场景:
  • 缓存:当你想缓存大量对象而又不想这些缓存阻止对象被回收时,弱引用是很有用的。
  • 循环引用:弱引用可以帮助解决循环引用问题,这些循环引用可能导致内存泄漏。
  • 观察者模式:当你使用观察者模式时,弱引用可以用来引用观察者而不真正拥有它们。
弱引用的实现:

Python提供了weakref模块来支持弱引用。以下是使用weakref的一些例子:

弱引用对象
import weakrefclass MyClass:passobj = MyClass() # 创建一个对象
r = weakref.ref(obj) # 创建一个弱引用print(r()) # 访问弱引用指向的对象del obj # 显式删除对象
print(r()) # 对象被删除后,弱引用返回None

在这个例子中,r是对obj的弱引用。当obj被删除时,r()将返回None

弱引用字典
import weakrefclass MyClass:passobj = MyClass()
weak_dict = weakref.WeakValueDictionary()weak_dict['primary'] = obj # 添加到弱引用字典print(weak_dict['primary']) # 获取对象,只要它还活着del obj # 删除对象
print(weak_dict.get('primary')) # 对象被回收,字典中不再包含它

WeakValueDictionary是一种特殊的字典,其值对对象只保持弱引用。这意味着如果没有其他强引用指向这些对象,它们可以被垃圾回收器回收。
使用弱引用时需要小心,因为如果你不持有对象的另一个活动引用,对象可能会在你不期望的时候被回收。此外,不是所有的对象都可以被弱引用;例如,列表和字典不能被直接弱引用,除非它们被子类化。

8. 使用__slots__

在定义类时使用__slots__来限制实例可以拥有的属性,这可以避免动态字典的使用,从而减少内存使用。

__slots__是一个类属性,用来声明固定的属性集合,并且能显著节省内存。默认情况下,Python中的每个类都会有一个__dict__属性,这是一个动态字典,允许我们在运行时为实例添加任意的新属性。虽然这提供了极大的灵活性,但这种动态分配也带来了额外的内存开销。当你预先知道类实例将拥有的属性集,并希望限制这些属性时,可以使用__slots__来代替实例的__dict__。通过这样做,每个实例不再拥有自己的属性字典,而是会在一个固定的小型数组中存储其属性,从而减少内存消耗。

使用 __slots__ 的优点:
  1. 内存节省:如果有数百万个实例,那么使用__slots__将大幅节省内存。
  2. 更快的属性访问:访问固定集合中的属性比访问__dict__中的属性更快,因为它不需要通过哈希表。
  3. 防止动态创建属性__slots__还可以防止动态创建不在__slots__定义中的属性,从而避免错误的属性赋值。
如何使用 __slots__
class MyClass:__slots__ = ['name', 'description']def __init__(self, name, description):self.name = nameself.description = descriptionobj = MyClass("Example", "This is an example.")# 尝试动态添加属性将会失败
try:obj.new_attribute = "Value"
except AttributeError as e:print(e)  # 'MyClass' object has no attribute 'new_attribute'

在上面的例子中,尝试给obj添加new_attribute是不允许的,因为new_attribute不在__slots__声明中。

注意事项:
  • 使用__slots__的类不能再给其实例动态添加不在__slots__中声明的属性。
  • 如果类定义了__slots__,那么其子类也需要定义__slots__以扩展父类的行为,否则子类实例将重新获得默认的__dict__
  • __slots__只对那些属性数量巨大的程序有实质性的内存节省。
  • __slots__中列出的属性名称必须是字符串。
  • 不应该使用__slots__仅仅为了防止类的用户新增属性。设计类的接口应通过文档和约定来控制,而不是通过强制限制。

9. 字符串优化:

在Python中,字符串是不可变的,这意味着一旦创建,它们的内容就不能被改变。不可变性带来了一些优点,比如线程安全和内存中只保存一份相同字符串的实例,但同时也意味着对字符串的修改操作可能会产生不必要的性能开销。以下是一些优化字符串操作的策略:

1. 避免在循环中连续拼接字符串

每次对字符串进行拼接操作时,因为字符串不可变,Python实际上会创建一个新的字符串并复制旧字符串的内容。在循环中连续拼接字符串特别低效,因为它会随着循环的进行而产生越来越多的临时字符串。

不推荐的方法

s = ""
for substring in list_of_strings:s += substring  # Inefficient

推荐的方法

使用str.join()方法在完成循环后一次性创建新的字符串。

s = "".join(list_of_strings)
2. 使用字符串格式化

当需要创建包含多个变量或表达式的字符串时,推荐使用字符串的格式化功能。Python提供了多种字符串格式化的方法。

旧式的%格式化

name = "John"
age = 30
s = "%s is %d years old." % (name, age)

str.format()方法

s = "{} is {} years old.".format(name, age)

f-strings(Python 3.6+)

s = f"{name} is {age} years old."

f-strings不仅阅读起来更简洁,通常也比其他字符串格式化方法更快,因为在运行时它们会转换为有效的字节码。

3. 使用生成器表达式而非列表推导式进行字符串拼接

当字符串拼接来自于对集合的迭代时,使用生成器表达式可以节省内存,因为它避免了创建整个列表。

s = "".join(str(number) for number in range(100))
4. 注意字符串不变性

对于字符串的某些“就地”修改看似不创建新的字符串实例,但实际上它们确实创建了。例如,str.replace()str.lower()str.upper()等方法会创建新的字符串,即使结果字符串与原字符串相同。

5. 使用内置方法处理字符串

内置的字符串方法(比如str.split(), str.strip(), str.find()等)经过优化,通常比手动实现的方法更快更高效。

6. 考虑使用intern方法

对于大量重复出现的字符串,可以使用intern方法。这个技术可以确保字符串在内存中只保存一份。这在某些特定场景下可以节省内存。

import sys
s = sys.intern('some long string')

10. 禁用调试工具:

确保在生产环境中禁用调试工具和详细的日志记录,因为它们可以占用大量的内存(如Flask和Django提供了一个调试模式,它会增加额外的日志记录、错误检查和其他诊断信息)。

11. 移除或限制内省和自省:

内省(introspection)和自省(self-inspection)技术,例如dir(), type(), repr(), locals(), globals()等,在调试时非常有用,但在生产环境中应当避免或最小化它们的使用。

相关文章:

Python的内存优化

在Python中,内存管理和优化是一个复杂的话题,因为它涉及到Python解释器的内部机制,特别是Python的垃圾收集和内存分配策略。Python通过自动垃圾收集机制管理内存,主要包括引用计数和标记-清除算法。 Python内存管理机制&#xff…...

蓝桥杯-回文日期[Java]

目录: 学习目标: 学习内容: 学习时间: 题目: 题目描述: 输入描述: 输出描述: 输入输出样例: 示例 1: 运行限制: 题解: 思路: 学习目标: 刷蓝桥杯题库日记 学习内容: 编号498题目回文日期难度…...

acwing算法基础之搜索与图论--树与图的遍历

目录 1 基础知识2 模板3 工程化 1 基础知识 树和图的存储:邻接矩阵、邻接表。 树和图的遍历:dfs、bfs。 2 模板 树是一种特殊的图(即,无环连通图),与图的存储方式相同。 对于无向图中的边ab,…...

前端uniapp请求真是案例(带源码)

目录 案例一案例二最后 案例一 <template><view class"box"><!-- <view class"title-back" click"backPrivious"><</view> --><!-- <view class"title-back" click"backPrivious"…...

MySQL -- mysql connect

MySQL – mysql connect 文章目录 MySQL -- mysql connect一、Connector/C 使用1.环境安装2.尝试链接mysql client 二、MySQL接口1.初始化2.链接数据库3.下发mysql命令4.获取执行结果5.关闭mysql链接6.在C语言中连接MySQL 三、MySQL图形化界面推荐 使用C接口库来进行连接 一、…...

如何用AI帮你下载安卓源码

以Android 11源码下载流程图如下所示&#xff1a; 1. 安装Git和Repo工具 2. 创建一个工作目录 3. 初始化仓库并下载源码 4. 切换到指定的分支 5. 编译源码 具体步骤如下&#xff1a; 安装Git和Repo工具&#xff1a;在Linux或Mac上&#xff0c;可以通过终端运行以下命令安装Gi…...

第三章:人工智能深度学习教程-基础神经网络(第三节-Tensorflow 中的多层感知器学习)

在本文中&#xff0c;我们将了解多层感知器的概念及其使用 TensorFlow 库在 Python 中的实现。 多层感知器 多层感知也称为MLP。它是完全连接的密集层&#xff0c;可将任何输入维度转换为所需的维度。多层感知是具有多个层的神经网络。为了创建神经网络&#xff0c;我们将神…...

Python的版本如何查询?

要查询Python的版本&#xff0c;可以使用以下方法之一&#xff1a; 1.在命令行中使用python --version命令。这会显示安装在计算机上的Python解释器的版本号。 # Author : 小红牛 # 微信公众号&#xff1a;wdPython2.在Python脚本中使用import sys语句&#xff0c;然后打印sy…...

Git的高效使用 git的基础 高级用法

Git的高效使用 git的基础 高级用法 前言 什么是Git 在日常的软件开发过程中&#xff0c;软件版本的管理都离不开使用Git&#xff0c;Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。 也是Linus Torvalds为了帮助管理Linu…...

关于主表和子表数据的保存

业务需求&#xff1a; 投注站信息保存在表A里&#xff0c;投注站下的设备信息保存在表B里&#xff0c; 一个投注站会有多个设备&#xff0c;要在一个表单里进行投注站和设备信息的填写&#xff0c;保存&#xff0c;回填&#xff0c;修改。 思路&#xff1a; 1&#xff09;将…...

如何在后台执行 SwiftData 操作

文章目录 前言Core Data 私有队列上下文SwiftData 并发支持使用 ModelActor合并上下文更改的问题通过标识符访问模型总结 前言 SwiftData 是一个用于处理数据操作的框架&#xff0c;特别是在 Swift 语言中进行并发操作。本文介绍了如何在后台执行 SwiftData 操作以及与 Core D…...

TCP和UPD协议

一)应用层协议简介:根据需求明确要传输的信息&#xff0c;明确要传输的数据格式&#xff1b; 应用层协议:这个协议&#xff0c;实际上是和程序员打交道最多的协议了 1)其它四层都是操作系统&#xff0c;驱动&#xff0c;硬件实现好了的&#xff0c;咱们是不需要管 2)应用层:当我…...

MySQL:锁机制

目录 概述三种层级的锁锁相关的 SQLMyISAM引擎下的锁InnoDB引擎下的锁InnoDB下的表锁和行锁InnoDB下的共享锁和排他锁InnoDB下的意向锁InnoDB下的记录锁&#xff0c;间隙锁&#xff0c;临键锁记录锁&#xff08;Record Locks&#xff09;间隙锁&#xff08;Gap Locks&#xff0…...

​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第1章-绪论-思维导图】 课本里章节里所有蓝色字体的思维导图...

【Git】安装和常用命令的使用与讲解及项目搭建和团队开发的出现的问题并且给予解决

目录 Git的简介 介绍 Git的特点及概念 Git与SVN的区别 图解 ​编辑 命令使用 安装 使用前准备 搭建项目环境 ​编辑 团队开发 Git的简介 介绍 Git 是一种分布式版本控制系统&#xff0c;是由 Linux 之父 Linus Torvalds 于2005年创建的。Git 的设计目标是为了更好地管…...

Python进行数据可视化,探索和发现数据中的模式和趋势。

文章目录 前言第一步&#xff1a;导入必要的库第二步&#xff1a;加载数据第三步&#xff1a;创建基本图表第四步&#xff1a;添加更多细节第五步&#xff1a;使用Seaborn库创建更复杂的图表关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Pyth…...

2023年中国自然语言处理行业研究报告

第一章 行业概况 1.1 定义 自然语言处理&#xff08;Natural Language Processing&#xff0c;简称NLP&#xff09;是一门交叉学科&#xff0c;它结合了计算机科学、人工智能和语言学的知识&#xff0c;旨在使计算机能够理解、解释和生成人类语言。NLP的核心是构建能够理解和…...

RISC-V与RISC Zero zkVM的关系

1. 引言 本文基本结构为&#xff1a; 编程语言背景介绍RISC-V虚拟机作为zkVM电路为何选择RISC-V&#xff1f; 2. 编程语言背景介绍 高级编程语言不专门针对某个架构&#xff0c;其便于人类编写。高级编程语言代码&#xff0c;经编译器编译后&#xff0c;会生成针对专门某架…...

20行JS代码实现屏幕录制

在开发中可能有遇到过屏幕录制的需求&#xff0c;无论是教学、演示还是游戏录制&#xff0c;都需要通过屏幕录制来记录和分享内容。一般在App内H5页基于客户端能力实现的较多&#xff0c;现在浏览器中的 MediaRecorder 也提供了这种能力。MediaRecorder 是一种强大的技术&#…...

基于springboot实现福聚苑社区团购平台系统项目【项目源码】

基于springboot实现福聚苑社区团购平台系统演示 Javar技术 Java是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…...

9-Oracle 23 ai Vector Search 特性 知识准备

很多小伙伴是不是参加了 免费认证课程&#xff08;限时至2025/5/15&#xff09; Oracle AI Vector Search 1Z0-184-25考试&#xff0c;都顺利拿到certified了没。 各行各业的AI 大模型的到来&#xff0c;传统的数据库中的SQL还能不能打&#xff0c;结构化和非结构的话数据如何和…...

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇&#xff1a;Apollo Client 配置与缓存 上一篇&#xff1a;GraphQL 入门篇&#xff1a;基础查询语法 依旧和上一篇的笔记一样&#xff0c;主实操&#xff0c;没啥过多的细节讲解&#xff0c;代码具体在&#xff1a; https://github.com/GoldenaArcher/graphql…...

算法打卡第18天

从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7…...