深入理解 Python 内存管理与垃圾回收(上)

简介: 再我们看文章之前,先思考一下:如果是你设计,会怎么进行内存管理?我们一起了解看看 Python 是怎么设计的。

先看看内存管理

内存的管理简单来说:分配(malloc)+回收(free)。


再我们看文章之前,先思考一下:如果是你设计,会怎么进行内存管理?答:好,不会设计(笔主也不会),会的大佬请绕过。我们一起了解看看 Python 是怎么设计的。为了提高效率就是:


  • 如何高效分配?
  • 如何有效回收?

什么是内存

买电脑的配置“4G + 500G / 1T”,这里的 4G 就是指电脑的内存容量,而电脑的硬盘 500G / 1T。


内存(Memory,全名指内部存储器),自然就会想到外存,他们都硬件设备。


内存是计算机中重要的部件之一,它是外存与 CPU 进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。

内存就像一本空白的书

首先,您可以将计算机的存储空间比作一本空白的短篇小说。页面上还没有任何内容。最终,会有不同的作者出现。每个作者都需要一些空间来写他们的故事。


由于不允许彼此书写,因此必须注意他们能书写的页面。开始书写之前,请先咨询书籍管理员。然后,管理员决定允许他们在书中写什么。


如果这书已经存在很长时间了,因此其中的许多故事都不再适用。当没有人阅读或引用故事时,它们将被删除以为新故事腾出空间。


本质上,计算机内存就像一本空书。实际上,调用固定长度的连续内存面块是很常见的,因此这种类比非常适用。


作者就像需要将数据存储在内存中的不同应用程序或进程。决定作者在书中书写位置的管理员就像是各种存储器管理的角色,删除旧故事为新故事腾出空间的人是垃圾收集者(garbage collector)。


内存管理:从硬件到软件

为什么 4G 内存的电脑可以高效的分析上 G 的数据,而且程序可以一直跑下去。


在这 4G 内存的背后,Python 都帮助我们做了什么?


内存管理是应用程序读取和写入数据的过程。内存管理器确定将应用程序数据放置在何处。


由于内存有限,类比书中的页面一样,管理员必须找到一些可用空间并将其提供给应用程序。提供内存的过程通常称为内存分配。


其实如果我们了解内存管理机制,以更快、更好的方式解决问题。


看完本篇文章,带您稍微了解 Python 内存管理的设计哲学。

对象管理

可能我们听过,Python 鼎鼎有名的那句“一切皆对象”。是的,在 Python 中数字是对象,字符串是对象,任何事物都是对象,Cpython 下,而 Python 对象实现的核心就是一个结构体--PyObject。

typedef struct_object{
  int ob_refcnt;
  struct_typeobject *ob_type;
}PyObject;


PyObject 是每个对象必有的内容,可以说是 Python 中所有对象的祖父,仅包含两件事:


  • ob_refcnt:引用计数(reference count)
  • ob_type:指向另一种类型的指针(pointer to another type)


所以,所以 CPython 是用 C 编写的,它解释了 Python 字节码。这与内存管理有什么关系?


好吧,C 中的 CPython 代码中存在内存管理算法和结构。要了解 Python 的内存管理,您必须对 CPython 本身有一个基本的了解。其他我们也不深究,感兴趣的同学自行了解。

CPython 的内存管理

下图的深灰色框现在归 Python 进程所有。


image.png


Python 将部分内存用于内部使用和非对象内存。另一部分专用于对象存储(您的 int,dict 等)。请注意,这已被简化。如果您需要全貌,则可以看 CPython 源代码,所有这些内存管理都在其中进行。


CPython 有一个对象分配器,负责在对象内存区域内分配内存。这个对象分配器是大多数魔术发生的地方。每当新对象需要分配或删除空间时,都会调用该方法。


通常,为 list 和 int 等 Python 对象添加和删除数据一次不会涉及太多数据。因此,分配器的设计已调整为可以一次处理少量数据。它还尝试在绝对需要之前不分配内存。


现在,我们来看一下 CPython 的内存分配策略。首先,我们将讨论这三个主要部分以及它们之间的关系。

Python 的内存分配器

内存结构

在 Python 中,当要分配内存空间时,不单纯使用 malloc/free,而是在其基础上堆放 3 个独立的分层,有效率地进行分配。


image.png


第 0 层往下是 OS 的功能。第 -2 层是隐含和机器的物理性相关联的部分,OS 的虚拟内 存管理器负责这部分功能。第 -1 层是与机器实际进行交互的部分,OS 会执行这部分功能。 因为这部分的知识已经超出了本书的范围,我们就不额外加以说明了。在第 3 层到第 0 层调用了一些具有代表性的函数,其调用图如下。


image.png

第 0 层 通用的基础分配器

以 Linux 为例,第 0 层指的就是 glibc 的 malloc() 这样的分配器,是对 Linux 等 OS 申 请内存的部分。


Python 中并不是在生成所有对象时都调用 malloc(),而是根据要分配的内存大小来改 变分配的方法。申请的内存大小如果大于 256 字节,就老实地调用 malloc();如果小于等 于 256 字节,就要轮到第 1 层和第 2 层出场了。


第 1 层 Python 低级内存分配器

Python 中使用的对象基本上都小于等于 256 字节,并且净是一些马上就会被废弃的对象。请看下面的例子。

for x in range(100):
  print(x)


上述 Python 脚本是把从 0 到 99 的非负整数 A 转化成字符串并输出的程序。这个程序会大量使用一次性的小字符串。


在这种情况下,如果逐次查询第 0 层的分配器,就会发生频繁调用 malloc() 和 free() 的情况,这样一来效率就会降低。


因此,在分配非常小的对象时,Python 内部会采用特殊的处理。实际执行这项处理的就是第 1 层和第 2 层的内存分配器。


当需要分配小于等于 256 字节的对象时,就利用第 1 层的内存分配器。在这一层会事先 从第 0 层开始迅速保留内存空间,将其蓄积起来。第 1 层的作用就是管理这部分蓄积的空间。

第 1 层处理的信息的内存结构

根据所管理的内存空间的作用和大小的不同,我们称最小 的单位为 block,最终返回给申请者的就是这个 block 的地址。比 block 大的单位的是 pool, pool 内部包含 block。pool 再往上叫作 arena。


image.png


也就是说 arena > pool > block,感觉很像俄罗斯套娃吧。为了避免频繁调用 malloc() 和 free(),第 0 层的分配器会以最大的单位 arena 来保留 内存。pool 是用于有效管理空的 block 的单位。arena 这个词有“竞技场”的意思。大家可以理解成竞技场里有很多个 pool,pool 里面漂 浮着很多个 block,这样或许更容易理解一些。

arena

Arenas 是最大的内存块,并在内存中的页面边界上对齐。页面边界是操作系统使用的固定长度连续内存块的边缘。Python 假设系统的页面大小为 256 KB。


image.png


Arenas 内有内存池,池是一个虚拟内存页(4 KB)。这些就像我们书中类比的页面。这些池被分成较小的内存块。


给定池中的所有块均具有相同的“大小等级”。给定一定数量的请求数据,大小类定义特定的块大小。


  • 针对小对象(<= 512 bytes),Pymalloc 会在内存池中申请内存空间
  • > 512bytes,则会 PyMem_RawMalloc()和 PyMem_RawRealloc()来申请新的内存空间


例如,如果请求 42 个字节,则将数据放入 48 字节大小的块中。

pool

arena 内部各个 pool 的大小固定在 4K 字节。因为几乎对所有 OS 而言,其虚拟内存的页 面大小都是 4K 字节,所以我们也相应地把 pool 的大小设定为 4K 字节。

第 1 层总结

第 1 层的任务可以用一句话来总结,那就是“管理 arena”。

第 2 层 Python 对象分配器

第 2 层的分配器负责管理 pool 内的 block。这一层实际上是将 block 的开头地址返回给申请者,并释放 block 等。 那么我们来看看这一层是如何管理 block 的吧。

block

pool 被分割成一个个的 block。我们在 Python 中生成对象时,最终都会被分配这个 block (在要求大小不大于 256 字节的情况下)。以 block 为单位来划分,这是从 pool 初始化时就决定好的。这是因为我们一开始利用 pool 的时候就决定了“这是供 8 字节的 block 使用的 pool”。pool 内被 block 完全填满了,那么 pool 是怎么进行 block 的状态管理的呢?block 只有以下三种状态。


  1. 已经分配
  2. 使用完毕
  3. 未使用

第 3 层 对象特有的分配器

对象有列表和元组等多种多样的型,在生成它们的时候要使用各自特有的分配器。

分配器的总结

image.png

相关文章
|
1月前
|
算法 Java Python
请简述Python中的垃圾回收机制。
请简述Python中的垃圾回收机制。
14 0
|
1月前
|
监控 算法 Java
如何确保Python的内存管理机制能够有效地工作?
【2月更文挑战第19天】【2月更文挑战第57篇】如何确保Python的内存管理机制能够有效地工作?
|
1月前
|
存储 Java Python
谈谈你对 Python 的内存管理机制的理解。
【2月更文挑战第19天】【2月更文挑战第55篇】谈谈你对 Python 的内存管理机制的理解。
|
10天前
|
存储 缓存 监控
Java内存管理:垃圾回收与内存泄漏
【4月更文挑战第16天】本文探讨了Java的内存管理机制,重点在于垃圾回收和内存泄漏。垃圾回收通过标记-清除过程回收无用对象,Java提供了多种GC类型,如Serial、Parallel、CMS和G1。内存泄漏导致内存无法释放,常见原因包括静态集合、监听器、内部类、未关闭资源和缓存。内存泄漏影响性能,可能导致应用崩溃。避免内存泄漏的策略包括代码审查、使用分析工具、合理设计和及时释放资源。理解这些原理对开发高性能Java应用至关重要。
|
14天前
|
存储 前端开发 安全
JVM内部世界(内存划分,类加载,垃圾回收)(上)
JVM内部世界(内存划分,类加载,垃圾回收)
48 0
|
24天前
|
存储 监控 异构计算
【Python】GPU内存监控脚本
【Python】GPU内存监控脚本
|
1月前
|
Python
Python中如何判断两个对象的内存地址是否一致?
Python中如何判断两个对象的内存地址是否一致?
17 0
|
1月前
|
算法 Java 开发者
详解python中的垃圾回收机制
详解python中的垃圾回收机制
37 0
|
1月前
|
Java Python
|
1月前
|
Java 程序员 Python

热门文章

最新文章