Python内部管理小对象内存 绕过系统调用抑制碎片

【python】内存管理结构初探——我要的内存从哪儿来的? — 码农高天

码农高天

Python通过Block与Pool三层结构托管512字节以下内存,仅在整个Arena彻底放空时才归还系统,导致进程内存占用释放极为迟缓。

AI-assisted summary of 【python】内存管理结构初探——我要的内存从哪儿来的? — original source. Check the original for full context.

Source published:

Key ideas

  • Python 针对 512 字节(SMALL_REQUEST_THRESHOLD)设立了分水岭:大于该阈值的大内存直接调用 C 语言 malloc 向操作系统申请,而小内存则进入内部三层架构管理,以避免频繁系统调用与内存碎片化。 (0:00)
  • 小内存管理采用 Block(固定大小数据块)、Pool(4KB 内存页)和 Arena(256KB 内存区)三层结构,并通过 8 字节内存对齐与 usedpools 数组实现极速检索与外部碎片消除。 (5:00)
  • 释放小对象时内存仅退回 Pool 的内部空闲链表,操作系统无法感知;现代 Python 仅在整整 256KB 的 Arena 内所有 Block 完全清空时,才会真正向操作系统执行 free。 (10:00)
  • 为提高内存归还效率,Python 调度分配时会优先使用最满的 Arena,促使低利用率 Arena 尽早彻底腾空以触发物理内存归还。 (10:00)

Detailed notes

## Background 该视频由 YouTube 频道“码农高天”于 2024 年 4 月 6 日发布。视频简介说明,本期内容将从高层视角(high level)简要探讨 Python 基础的底层内存管理结构,拆解 Python 究竟是如何分配与管理内存的。

## TL;DR 视频深入剖析了 Python 申请与释放内存的底层机制。绝大多数对象创建均通过 PyObject_Malloc,并以 512 字节为界:大于 512 字节的大内存直接交由 C 语言 malloc 向操作系统申请;而小于等于 512 字节的小内存则走内部专属的 pymalloc 架构。该架构通过 Block、4KB 的 Pool 以及 256KB 的 Arena 三层层级结构与 usedpools 索引数组进行 8 字节对齐管理,以消除碎片化并规避频繁系统调用的开销。在释放时,小对象仅归还到 Pool 的空闲链表而不会通知操作系统,只有当整个 Arena 完全空闲时才会统一归还给系统。

## Atmosphere & style 讲解风格通俗务实、条理极其严密,展现了硬核程序员拆解底层代码时的从容与笃定。高天善于运用生动的类比(如“排排队”拿固定规格内存)化繁为简,将抽象复杂的 C 语言底层指针与分页机制讲得清晰易懂。整段解析既有自顶向下的架构全景,又有微观到 8 字节对齐的细节印证,听来让人产生一种“知其然更知其所以然”的顿悟感与技术踏实感。

## Bulletin - [00:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=0s) Python 针对 512 字节(SMALL_REQUEST_THRESHOLD)设立了分水岭:大于该阈值的大内存直接调用 C 语言 malloc 向操作系统申请,而小内存则进入内部三层架构管理,以避免频繁系统调用与内存碎片化。 - [05:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=300s) 小内存管理采用 Block(固定大小数据块)、Pool(4KB 内存页)和 Arena(256KB 内存区)三层结构,并通过 8 字节内存对齐与 usedpools 数组实现极速检索与外部碎片消除。 - [10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) 释放小对象时内存仅退回 Pool 的内部空闲链表,操作系统无法感知;现代 Python 仅在整整 256KB 的 Arena 内所有 Block 完全清空时,才会真正向操作系统执行 free。 - [10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) 为提高内存归还效率,Python 调度分配时会优先使用最满的 Arena,促使低利用率 Arena 尽早彻底腾空以触发物理内存归还。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=0s) 绝大多数 Python 自定义类对象在内存申请时均走 PyType_GenericAlloc,并最终汇聚至底层核心入口 PyObject_Malloc(_PyObject_Malloc)。 - [00:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=0s) Python 以 512 字节为界限:大于 512 字节通过 PyMem_RawMalloc 直接调用系统 malloc,小于等于 512 字节则进入 pymalloc 专属小内存分配体系。 - [00:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=0s) Python 运行中会频繁创建与销毁诸如临时字典、列表等微小对象,若全部直调 malloc 会因频繁系统调用产生巨大开销并带来严重的内存碎片化。 - [05:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=300s) Python 小内存层级由 Block(最小数据块)、Pool(4KB,对应操作系统内存页)与 Arena(256KB,默认预分配 16 个)三层容器构成。 - [05:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=300s) Pool 内部的所有 Block 规格必须完全一致,但不同规格 Pool 之间的尺寸由其激活转为“使用中”时动态确定,从而彻底消除了物理不连续导致的外部碎片。 - [05:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=300s) Python 对小内存统一实施 8 字节向上对齐(例如 1-8 字节对齐为 8 字节,22 字节对齐为 24 字节),因此 Python 内部对象的字节大小始终可被 8 整除。 - [05:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=300s) 核心结构 usedpools 本质是一个按照 8 字节步长索引的 Pool 指针数组,内存申请时仅需规整尺寸后取索引即可直接拿到空闲 Block,大幅提升分配效率。 - [10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) 释放微小对象时,系统仅是将 Block 放回所属 Pool 的空闲链表以备下次复用,操作系统完全感知不到内存的释放操作。 - [10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) 早期 Python 对 Arena 体系的内存只增不减;现代 Python 虽支持归还,但向操作系统申请与归还的最小物理单位是 256KB 的 Arena。 - [10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) 只要某个 Arena 内存在任何一个 Block 仍在使用,整个 Arena 就无法向操作系统释放,这解释了为何 Python 进程占用的物理内存在宏观上归还极为迟缓。 - [10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) Python 通过将分配压力集中在“最满”的 Arena 上,促使低利用率的 Arena 尽快彻底清空并归还系统,在复用内存与控制进程膨胀之间取得平衡。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=0s) Python 申请内存的入口与为什么不直接用 malloc

绝大部分自定义 class 在申请内存时,走到的都是 `PyType_GenericAlloc` 函数。该函数首先会判断目标类型是否为一个容器对象、是否存在循环引用的可能;如果是容器,Python 需要对其做额外处理以防内存泄漏。但不管是不是容器,Python 申请内存的核心入口最终都会汇聚到 `PyObject_Malloc`,并进一步调用内部的底层实现 `_PyObject_Malloc`。

在这个正式的申请入口处会出现一个分水岭:系统会先尝试通过 `pymalloc_alloc` 申请内存,若失败则退回到 `PyMem_RawMalloc`。绝大多数申请“失败”并非内存耗尽,而是因为检查条件命中——即申请字节数 `nbytes` 是否大于小内存阈值(`SMALL_REQUEST_THRESHOLD`,固定为 512 字节)。当请求的内存大小大于 512 字节时,Python 就会走 `PyMem_RawMalloc`,其底层非常纯粹,直接调用 C 语言的 `malloc`,需要多少就直接管操作系统要多少。

很多人会下意识认为 Python 创建任何一个对象都是直接去向操作系统 `malloc`,但码农高天指出事实并非如此:“因为我们知道 Python 这个语言在运行的过程中会建立非常多小的 object,比如说在函数参数调用的过程中,它就会建立小的 dictionary 或者小的 list。”如果频繁为这些微小对象直接调用系统 `malloc`,不仅会因为潜在的系统调用(System Call)产生巨大的开销(overhead),更严重的是会产生海量的内存碎片。Python 具有创建后立刻释放、频繁分配与销毁的语言特性,反复向系统索取和归还小块内存会导致内存极度碎片化,因此 Python 必须在底层建立一套专属的内部内存管理系统。

```mermaid flowchart TD accTitle: Python 内存分配入口逻辑 accDescr: 描述 Python 对象内存申请时判断大小并分流到专属内存池或系统 malloc 的过程

subgraph Entry ["内存申请入口"] A["PyType_GenericAlloc"] --> B["PyObject_Malloc / _PyObject_Malloc"] end

subgraph Decision ["大小判断"] B --> C{"申请大小 <= 512 bytes?"} end

subgraph Allocators ["分配路径"] C -->|"是 (小内存)"| D["pymalloc_alloc<br>(内部三层架构管理)"] C -->|"否 (大内存)"| E["PyMem_RawMalloc<br>(直接调用 C 语言 malloc)"] end ```

- `PyType_GenericAlloc`:绝大多数自定义 class 分配内存的统一入口。 - `SMALL_REQUEST_THRESHOLD`:Python 小对象内存分配的阈值,固定为 512 字节。 - 大内存(> 512 字节):由 `PyMem_RawMalloc` 直通 C `malloc` 处理。 - 小内存(<= 512 字节):进入 Python 专属的小内存分配系统(pymalloc)。

---

[05:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=300s) 三层层级结构:Block、Pool、Arena 与 usedpools

Python 的小内存管理系统由小到大分为三层容器结构: 1. **Block(数据块)**:最小的单位,每一个 block 保存固定大小的数据。 2. **Pool(内存池)**:每个 pool 大小通常为 4KB,刚好对应操作系统的一个内存页(Page)。一个 pool 内的所有 block 大小完全相同,因此每个 pool 会维护一个 `size` 属性。不同 pool 之间的 block 规格可以不同,但单一 pool 内部的 block 规格是严格一致的。 3. **Arena(内存区)**:每个 arena 的默认大小为 256KB。一个 arena 内部包含多个 pool。在初始状态下,arena 主要记录未被使用的 pool。特别的是,pool 内部的 block 尺寸并不是一开始就固定的,而是在该 pool 从“未使用”转变为“使用中”的那一刻才动态决定的。换言之,所有 pool 的物理总大小一致,但由于内部划分的 block 尺寸不同,所能容纳的 block 数量也不同。Python 启动时会预先建立一堆 arena(通常是 16 个),用尽后才会申请新的 arena。

管理这套架构的核心枢纽是 `usedpools`。可以将其理解为一个存放 pool 指针的数组,该数组的索引(index)直接与 block 的尺寸绑定: - `index 0` 对应的 pool,其内部每个 block 为 8 字节; - `index 1` 对应的 pool,其内部每个 block 为 16 字节; - `index 2` 对应的 pool,其内部每个 block 为 24 字节;以此类推。

当小内存申请到来时,Python 首先进行“内存对齐”(Memory Alignment):申请 1 到 8 字节统一给 8 字节,申请 9 到 16 字节统一给 16 字节。码农高天强调:“所以当你看 python 里面那个 object 的大小的时候,你会发现它的 byte 数永远是可以被 8 整除的。”对齐后,系统直接根据规整后的大小取索引,去 `usedpools` 对应的 pool 里面随便取一个空闲 block 分配出去。例如申请 22 字节,对齐后为 24 字节,Python 就会精准定位到 `index 2` 的 pool,直接摘下一个未使用的 24 字节 block。

之所以采用如此严格的分类机制,是为了彻底解决碎片化问题。如果随意连续申请 10 块 50 字节的内存,随后将第 1、3、5、7、9 块释放,内存空间就会变得千疮百孔;此时若想申请 100 字节,即使空闲总和有 250 字节,也因为物理不连续而无法使用,只能重新开辟。而 Python 的逻辑是“排排队”:申请 32 字节就去 32 字节规格的池子里拿,释放时也还回该池子;由于池内每个 block 规格一样,随便拿出一个都能满足需求,既消除了外部碎片,又大幅提升了分配速度。

```mermaid flowchart TD accTitle: Python 内存管理三层容器层级 accDescr: 展示 Arena、Pool、Block 三层包含关系以及 usedpools 索引映射机制

subgraph ArenaLevel ["Arena 层 (256 KB)"] AR["Arena (预分配 16 个)"] end

subgraph PoolLevel ["Pool 层 (4 KB / 内存页)"] AR --> P1["Pool A (8B blocks)"] AR --> P2["Pool B (24B blocks)"] AR --> P3["Pool C (未激活/规格未定)"] end

subgraph UsedPoolsArray ["usedpools 索引数组"] UP0["index 0 (8 Bytes)"] -.-> P1 UP2["index 2 (24 Bytes)"] -.-> P2 end

subgraph BlockLevel ["Block 层 (数据块)"] P1 --> B1["Block (8B)"] P1 --> B2["Block (8B)"] P2 --> B3["Block (24B)"] end ```

- Pool 大小:4KB,与单个内存页等大。 - Arena 大小:256KB,Python 初始化时默认建立 16 个。 - 内存对齐规则:按 8 字节向上取整(例如 5 字节对齐为 8 字节,22 字节对齐为 24 字节),对象占用的字节数必然能被 8 整除。

---

[10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) 对象的释放机制与 Arena 的回收策略

当 Python 创建一个小对象并获取内存时,拿到的是属于某个 pool 的 block,而该 pool 归属于某个 arena。这种预先批发的架构让获取小内存时的开销变得极低,不需要频繁打扰操作系统。

然而在对象释放(free)时,这种机制也带来了一个关键现象:当用户释放一个 Python 对象,系统仅仅是将该 block 放回所属 pool 的 free block 空闲链表里,标记为“又可以使用了”,并没有将其真正归还给操作系统。码农高天总结道:“换句话说,在绝大部分情况下,当你在 Python 里面建立一个 object、销毁一个 object,如果这个 object 比较小,操作系统压根就不知道。”

这引出了一个核心疑问:Python 是否会一直霸占这些内存不归还? - **早期版本**:走 arena 这一套体系的内存是只增不减、从来不向操作系统执行 `free` 的(走 `raw malloc` 的大内存除外)。 - **现代版本**:现代 Python 已经支持向操作系统释放内存,但其释放与申请的最小物理颗粒度都是以 **Arena** 为单位的。“也就是说,但凡你这个 arena 里面有一个 pool 的一个 block 还正在被使用,这个 arena 就没有办法被 free 掉。”

为了改善回收效率,Python 在内部做了一定优化:在需要申请新的 pool 时,会优先从“最满的”(即使用率最高、剩余空闲 pool 最少)arena 里面分配。通过对所有 arena 进行排序调度,Python 尽量把分配压力集中在部分 arena,从而让那些使用比例极小的 arena 更有机会彻底清空所有 block,进而被整体归还给操作系统。

只要没有将所有已存在 arena 中的 pool 全部耗尽,Python 就不会申请新的 arena,而是反复复用现有内存。因此这一机制并不会导致进程内存无休止膨胀,但确实会导致一个直观体验:Python 真正把物理内存还给操作系统的节奏往往十分迟缓。

- 归还逻辑:小对象的 free 只是把 block 归还给 pool 的内部空闲链表,操作系统无法感知。 - 操作系统级 Free:只有当整个 256KB 的 arena 内的所有 block 全部空闲时,该 arena 才会真正调用系统 free。 - 排序优化:优先向使用率最高的 arena 索取新的 pool,促使低利用率 arena 尽快清空以释放物理内存。

## Notable quotes > [10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) 在绝大部分情况下,当你在 Python 里面建立一个 object、销毁一个 object,如果这个 object 比较小,操作系统压根就不知道。 — 码农高天

> [10:00](https://www.youtube.com/watch?v=igB-hkESMeI&t=600s) 但凡你这个 arena 里面有一个 pool 的一个 block 还正在被使用,这个 arena 就没有办法被 free 掉。 — 码农高天

## Mentioned 码农高天, Python, PyType_GenericAlloc, PyObject_Malloc, _PyObject_Malloc, pymalloc_alloc, PyMem_RawMalloc, malloc, free, usedpools, Block, Pool, Arena, C 语言

## Video description 这期视频我们聊一下基础的python内存管理结构,从high level的角度简单聊一下,python是如何管理内存的。

[Source](https://www.youtube.com/watch?v=igB-hkESMeI)

中文简报

Python内部管理小对象内存 绕过系统调用抑制碎片 Python通过Block与Pool三层结构托管512字节以下内存,仅在整个Arena彻底放空时才归还系统,导致进程内存占用释放极为迟缓。 视频深入剖析了 Python 申请与释放内存的底层机制。绝大多数对象创建均通过 PyObject_Malloc,并以 512 字节为界:大于 512 字节的大内存直接交由 C 语言 malloc 向操作系统申请;而小于等于 512 字节的小内存则走内部专属的 pymalloc 架构。该架构通过 Block、4KB 的 Pool 以及 256KB 的 Arena 三层层级结构与 usedpools 索引数组进行 8 字节对齐管理,以消除碎片化并规避频繁系统调用的开销。在释放时,小对象仅归还到 Pool 的空闲链表而不会通知操作系统,只有当整个 Arena 完全空闲时才会统一归还给系统。 Python 针对 512 字节(SMALL_REQUEST_THRESHOLD)设立了分水岭:大于该阈值的大内存直接调用 C 语言 malloc 向操作系统申请,而小内存则进入内部三层架构管理,以避免频繁系统调用与内存碎片化。 (0:00) 小内存管理采用 Block(固定大小数据块)、Pool(4KB 内存页)和 Arena(256KB 内存区)三层结构,并通过 8 字节内存对齐与 usedpools 数组实现极速检索与外部碎片消除。 (5:00) 释放小对象时内存仅退回 Pool 的内部空闲链表,操作系统无法感知;现代 Python 仅在整整 256KB 的 Arena 内所有 Block 完全清空时,才会真正向操作系统执行 free。 (10:00) 为提高内存归还效率,Python 调度分配时会优先使用最满的 Arena,促使低利用率 Arena 尽早彻底腾空以触发物理内存归还。 (10:00)

Reading summary

码农高天指出,Python 在底层并未直接为每个对象调用系统 malloc,而是以 512 字节为界,通过三层结构专属内存池管理高频创建的小对象,以降低系统调用开销并消除内存碎片。 内存入口与 512 字节分水岭 (0:00) 在 Python 中,绝大多数自定义 class 在申请内存时均通过 `PyType_GenericAlloc` 进入,最终汇聚到底层的 `PyObject_Malloc` 与 `_PyObject_Malloc`。在此入口处,系统会根据阈值 `SMALL_REQUEST_THRESHOLD`(固定为 512 字节)进行分流:申请大于 512 字节的大内存会经由 `PyMem_RawMalloc` 直接调用 C 语言的 `malloc`,向操作系统索取空间。 针对小于等于 512 字节的小内存,系统则转入 `pymalloc_alloc` 机制。码农高天解释称,Python 在运行过程中会频繁创建并销毁函数参数字典或列表等微小对象,若直接对这些对象调用系统 malloc,不仅会因潜在的系统调用带来巨大开销,更会导致严重的内存外部碎片化,因此必须依赖内部专属分配器进行管控。 Block、Pool 与 Arena 三层架构 (5:00) Python 的小内存管理系统自底向上划分为三层容器:最小单元为固定尺寸的 Block;中间层为 Pool,大小通常为 4KB(对应操作系统的单个内存页);最上层为 Arena,默认大小为 256KB,Python 启动时通常会预先建立 16 个。Pool 在从未使用转为使用中时动态确立其内部 Block 的规格,因此单一 Pool 内的所有 Block 规格严格一致,但容纳数量随规格而变化。 小内存的调度由指针数组 `usedpools` 驱动,其索引与 Block 尺寸绑定。所有小内存申请均遵循 8 字节向上取整的对齐规则(例如申请 22 字节对齐为 24 字节),因此 Python 对象的字节数均能被 8 整除。系统对齐后直接按索引定位到 `usedpools` 中对应尺寸规格的 Pool 提取空闲 Block。这种按固定规格归类分配的方式避免了不同大小对象交替释放留下的物理不连续间隙,从而消除了外部碎片并提高了分配效率。 对象释放机制与物理内存归还滞后 (10:00) 当一个小对象被销毁时,Python 仅将对应的 Block 放回其所属 Pool 的空闲链表中标记为可用,整个过程操作系统并无法感知。由于内存预先由 Python 批发管理,对象的创建与销毁在绝大多数情况下都不会触发系统级的内存变动。 向操作系统真正释放内存(Free)的最小物理颗粒度是 256KB 的 Arena。只要某个 Arena 中仍有任何一个 Pool 的一个 Block 在被使用,该 Arena 就无法归还给系统。为了提高回收效率,现代 Python 会对 Arena 进行排序调度,在需要新 Pool 时优先向使用率最高的 Arena 索取,促使低利用率的 Arena 彻底清空。未耗尽现有 Arena 前系统不会开辟新区,因此内存不会无限膨胀,但这也解释了为何 Python 进程归还物理内存的节奏通常较为迟缓。 总体而言,Python 通过将小对象分配限制在内部 Block、Pool 与 Arena 架构中,在执行速度与碎片控制上取得了平衡,但以 Arena 为单位的物理释放机制也决定了其向操作系统退还内存存在天然的滞后性。

Source quotes

[10:00] 码农高天: 在绝大部分情况下,当你在 Python 里面建立一个 object、销毁一个 object,如果这个 object 比较小,操作系统压根就不知道。 [10:00] 码农高天: 但凡你这个 arena 里面有一个 pool 的一个 block 还正在被使用,这个 arena 就没有办法被 free 掉。

Original transcript

[0:00] 这期视频我们来讲一下Python的内存管理当然呢 因为这个话题本身实在太大了所以我们今天只能聊一小部分我们带大家入一个门简单的介绍一下Python的内存管理的结构从一个high level的角度呢给大家一个基本的印象那未来如果有机会的话我们会再继续讨论其他的内容包括一些实现的细节好我们看这个函数啊PiType Generic Alloc我们绝大部分自定义的class在申请内存的时候都使用的是这个函数那在中间这里他先判断了一下这个type简单的说就是这个东西是不是一个容器有没有可能造成循环引用如果是的话python需要对它做一些处理以防内存泄漏但是不管它是与不是Python最终使用的申请内存的函数入口都是这个py object malloc这个malloc函数调用的是它内部的一个下滑线py object 它的malloc函数那你绕了一大圈就会发现他最终使用的函数是我们面前的这个函数啊下方线: pi object malloc这里就是我们正式的申请内存的入口了在这个地方我们会有两个分支他首先会尝试用πmaloc alloc函数如果这个函数申请内存失败了他就会用下面的PyMap RawMap看数那什么情况下会申请失败呢我们看绝大多数申请失败的情况都是在这个判断也就是Nbytes是不是大于small request threshold这个small request threshold是512也就是说,当你申请的内存的大小小于等于512个byte的时候他使用的是我们这个函数而如果你申请内存的大小大于512个byte了他就会用raw malloc那raw malloc这个部分其实非常简单它使用的就是c的 malloc函数也就是你需要多少内存你就管系统要多少就可以了可能很多人听到这句话的时候就稍有点懵说哎 派档难道不是你需要多少内存就去管系统要多少吗他难道不是在建立每一个object时候都做一个maloc吗当然不是啊Python有一套自己内部的内存管理系统因为我们知道 Python 这个语言在运行的过程中会建立非常多小的object比如说在函数参数调用的过程中他就会建立小的dictionary或者小的list而如果每一次建立小的object,我们都使用malloc直接管系统要内存的话第一会产生比较大的overhead因为这有可能会发展成一个system call第二更重要的是它会产生大量的碎片内存因为python语言本身的特性很多东西都是我建立了然后我就free掉建立了free掉这一来一去内存碎片化会非常严重于是PiDOT就建立了一套属于自己的内存管理系统这就是我们今天的主题但是 这套内存管理系统非常繁杂 尤其是它的实现异常的复杂读C代码会读的很痛苦所以我们今天尝试从一个hello的角度跟大家介绍一下这个系统是什么样的我们会尝试使用大家更熟悉的拍档语言来描绘这个内存管理系统的架构那在描绘的过程中 当然有很多地方是不精确的 甚至不准确的但是我们这期视频的目的本身就不是在给大家介绍具体的内存管理系统的实现而是尝试给大家一个最基础的概念Python内存管理的层级或者我们可以叫container由小到大一共有三种类型最小的叫做block每一个block保存一个固定大小的数据往上一层叫做 pool一个pull一般是4k和一个内存液一样大每个pool里面都是相同大小的block这里注意一个pool和另外一个pool 他们俩拥有的block的大小可能不一样大 但是在每一个pool的内部 每一个block是一样大的因此每一个pool实际上是有一个size属性的这个size基本上就是拿来说明这个pool里面block的大小是多少那比破 [5:00] 更大的叫arena每一个arena一般是256k同样的每一个arena里面也有很多pool但是arena里面基本上只记录还没有被使用的pool而这个pool里面block的大小是在这个pool从没使用转变成使用的那一刻才决定的换句话说每一个pool的大小是一模一样的只是它里面保存的block的大小不一样也就意味着每个pool里面保存的block的数量是不一样的python在刚开始运行的时候会建立一堆arena一般来说是16个当然如果后面arena不够用了它就会建立新的arenaarena的概念是比较容易理解的它就是一大坨一大坨的内存接下来非常重要的一个概念点叫used poolsused pools你可以把它理解为一个保存着很多的pool指针的数组这里面它有一个更重要的概念就是这个数组的index对应着这个pool它里面block的大小也就是你可以简单的理解成在这个usedpools里面在index0的这个pool它里面每一个block是8个byte在index1的这个pool它里面每一个block是16个byte然后index2的这个pool它里面每一个block是24个byte当一段代码向python申请内存的时候Python首先会判断你申请的这个内存是不是大于512如果是的话直接去用c的maloc如果你申请的内存小于512的话他会先给你做一个内存对齐什么叫内存对齐呢比如你需要5个byte他就给你8个byte就你需要1个byte到8个byte他都给你8个byte你需要9个byte到16个byte它都给你16个byte这样的目的也是为了减少内存的碎片化所以当你看python里面那个object的大小的时候你会发现它的byte数永远是可以被8整除的那在做了这个取整之后我相信大家都应该能猜到python就会去对应的pool里面找一个block给你比如说我现在申请22个byte那python就会去对应的pool里面找一个block给你比如说我现在申请22个byte那python说好你申请22我就给你24个byte而24个byte对应的这个pool是在这个usedpool里面的第三个pool或者说是index为2的那个pool这个pool里面每一个block都是24个byte我就可以从这个pool里面随便拿一块没有使用到的block给你这样就完成了一次内存的分配那为什么要把这个内存管理做的这么复杂或者说我们刚才提到的所谓内存碎片化是个什么东西我们举个例子比如说我连续索取了10块50个byte的内存然后我把我第13579个索取的内存都free掉了这个时候你脑海中就可以想象出来一个千疮百孔的内存序列当我再想要100个byte的时候你是没有办法把之前free出来的那个内存给我的因为那50个50个都不连续你只能再新开出来100个给我那长此以往在不规律的索取和返还之下你的内存碎片化就会越来越严重那python对此的解决方法是你先给我排排队就你申请一个32 byte的内存你就从这个只拥有32 byte的block的pool里面拿你返还的时候也是往那返还那同样的你申请64个byte你就往你64个byte那个地方去找那在这种情况下呢内存明显就规整了很多啊那实际上这种内存分类的制度是有助于这个速度的提升的因为如果你需要32个byte我只需要去看32个byte里面那个pool里面所有的block就行了我随便扔给你一个都是可以的接下来我想聊一下这个结构对于python来说意味着什么我们可以看到当python申请一小块内存的时候它拿到的实际上是一个block这个block属于一个pool这个pool属于一个arena由于这样的结构设置当我们拿一小块内存的时候它的overhead是很低的它不需要再去向系统重新申请内存但是从另一个角度看当我们free一个object时候它也只是把我使用的这个block的内存给还回去了还到了这个内存 [10:00] 管理系统里面不用的block就变成了pull的free block链表里面的一部分也就是说这个block只是被标记成了 又可以使用了他并没有被归还到操作系统换句话说在绝大部分情况下当你在python里面建立一个object销毁一个object如果这个object比较小操作系统压根就不知道那接下来问题就来了拍档的内存管理系统是会一直拽着这些内存不放吗他有没有free的时候事实上在Python比较早期的版本里面python maloc.net 内存是从来不free的当然我们说的是arena这个部分啊如果你这个东西很大的话他走的是那个raw manlock的话他当然会free但是走python这个arena pool block层级的内存是从来不服read的而现在的python是会free内存的但是他只会free掉所有的内存都没有在被使用的Arena他的Free和Malloc都是以Arena为级别进行的也就是说 但凡你这个arena里面有一个pool的一个block还正在被使用 这个arena就没有办法被free掉当然 Python 的内部对这件事情是稍微有做一些优化的比如说当你申请一个新的pool的时候他会优先从最满的arena里面给你拿pool也就是说它所有的arena是进行过一个sort的这样可以尽可能的让那些使用比例比较小的arena有可能被free掉从而避免占据过多的内存那当然只要我们仔细的想一下就会发现这个机制并不会导致Python占用内存无限的上升因为我们说过每一个arena里面的每一个pool都是等价的所以只要你没有把所有Arenas里面的Pool都用满你就并不会建立新的Arena你再申请Pool的时候还是从现有的Arena里面去拿内存只是说Python真正的把内存还给操作系统的速度可能会有点慢好那这期视频的内容就到这里希望大家有所帮助喜欢的话记得点一下关注别忘了意见三连哦字幕志愿者 杨茜茜