3、内存碎片概念:内存碎片定义、内部碎片与外部碎片、碎片产生的根本原因

在嵌入式系统开发中,内存碎片是导致系统长期运行后出现分配失败、性能下降甚至崩溃的隐形杀手。对于TFLite Micro这类运行在资源极度受限的MCU上的推理引擎而言,理解内存碎片的本质是进行内存优化和碎片治理的前提。本章将系统性地剖析内存碎片的定义、分类及其产生的根本原因,为后续分析TFLite Micro的“碎片假象”与“实际占用”奠定理论基础。

内存碎片概念全景图 内存碎片 内部碎片 (Internal Fragmentation) 分配单元内部未被使用的空间 例如:分配16字节,实际使用10字节 浪费在分配单元“内部” 外部碎片 (External Fragmentation) 空闲块散布在已分配块之间 总空闲空间足够,但无连续大块 浪费在分配单元“之间” 碎片产生的根本原因 分配/释放顺序 随机交错模式 请求大小多样性 大小差异悬殊 分配器策略 First-fit/Best-fit等 生命周期差异 短期/长期共存

3.1 内存碎片的定义与本质

内存碎片(Memory Fragmentation)是指内存空间中存在无法被有效利用的“空洞”或“浪费区域”的现象。从系统整体视角看,碎片意味着可用内存总量充足,但实际可分配给应用程序的连续内存块却不足。在TFLite Micro的上下文中,这种“总量充足但无法使用”的矛盾尤为突出——因为MCU的RAM通常只有几十到几百KB,任何形式的碎片都可能导致模型加载或推理失败。

碎片的本质是内存分配模式与释放模式在时间和空间上的不匹配。当程序以不同的顺序、不同的大小请求内存,并以非对称的方式释放时,内存空间就会被逐渐“切割”成越来越小的块。对于TFLite Micro而言,其内存分配主要发生在模型加载阶段(权重、中间张量、算子缓冲区),而释放则发生在推理结束或会话销毁时,这种“集中分配、集中释放”的模式看似不易产生碎片,但实际由于张量生命周期不同、arena复用策略等因素,碎片问题依然存在。

关键理解:碎片不是“内存泄漏”——泄漏是内存完全丢失,而碎片是内存仍然存在但无法被有效利用。在嵌入式系统中,碎片比泄漏更隐蔽,因为它不会立即导致OOM(Out Of Memory),而是让系统在运行一段时间后“莫名其妙”地分配失败。

3.2 内部碎片与外部碎片的深入对比

内存碎片根据其产生位置和表现形式,分为内部碎片和外部碎片两大类。理解两者的区别是选择优化策略的前提。

3.2.1 内部碎片:分配单元内部的浪费

内部碎片发生在已分配的块内部。当分配器按照固定大小(如16字节、32字节对齐)或向上取整的方式分配内存时,如果请求的大小不是分配粒度的整数倍,就会在块尾部产生无法使用的剩余空间。例如,TFLite Micro的arena分配器通常按8字节或16字节对齐,若某张量需要30字节,实际分配32字节,则2字节成为内部碎片。

内部碎片的特点:

  • 产生时机:在分配发生时即确定,与后续释放顺序无关。
  • 大小固定:每个分配块产生的内部碎片大小是确定的,可计算。
  • 不可回收:只要该块未被释放,内部碎片就无法被其他请求使用。
  • 累积效应:大量小块的内部碎片累积起来,可能占据可观的内存比例。

3.2.2 外部碎片:空闲块之间的割裂

外部碎片发生在已分配块之间的空闲区域。当程序多次分配和释放不同大小的内存块后,空闲内存被分割成许多不连续的小块。虽然这些小块的总和可能满足新请求的大小需求,但由于它们不连续,无法分配给需要连续内存的请求。在TFLite Micro中,外部碎片通常出现在arena的多次复用场景中——例如,当不同算子的中间张量在不同时间点被分配和释放时,arena中会留下大小不一的空洞。

外部碎片的特点:

  • 产生时机:在多次分配/释放循环后逐渐形成。
  • 大小动态变化:随分配模式的变化而改变。
  • 可部分回收:通过合并相邻空闲块可以恢复部分连续空间。
  • 难以预测:其严重程度取决于分配序列的复杂程度。
对比维度 内部碎片 外部碎片
位置 分配块内部 分配块之间
产生原因 对齐、向上取整、固定块大小 分配/释放顺序不匹配、大小差异
可预测性 高(分配时即可计算) 低(依赖运行时行为)
回收方式 仅随块释放而消失 可通过空闲块合并回收
对TFLite Micro的影响 arena对齐导致的字节浪费 多次推理后arena空洞增多

3.3 碎片产生的根本原因分析

碎片并非随机产生,而是由几个确定的机制驱动。理解这些根本原因,才能针对性地设计碎片抑制策略。

3.3.1 分配与释放的顺序不对称

这是外部碎片产生的首要原因。当内存块的分配顺序与释放顺序不一致时,空闲块会被“打散”。例如,假设依次分配A(100B)、B(50B)、C(80B),然后释放B,此时空闲块为50B;接着请求70B的内存,虽然总空闲空间为50B+剩余空间,但50B的块无法满足70B的请求。在TFLite Micro中,如果模型的不同算子在不同时间创建和销毁中间张量,这种不对称性就会在arena中累积。

3.3.2 请求大小的多样性

如果所有内存请求的大小都相同,那么释放后的块可以满足任意新请求,碎片几乎不会产生。但实际中,TFLite Micro的张量大小差异巨大——权重可能占用数KB,而中间激活值可能只有几十字节。这种大小差异导致释放的小块无法满足大块请求,而释放的大块又会被后续的小请求“切碎”。

3.3.3 分配器的策略选择

不同的分配策略对碎片的影响显著:

  • First-fit(首次适应):分配速度快,但容易在低地址区域产生大量小碎片。
  • Best-fit(最佳适应):减少大块被切碎的风险,但分配速度慢,且可能留下极小的不可用碎片。
  • Buddy system(伙伴系统):通过2的幂次大小分配,合并容易,但内部碎片可能较大。
  • Slab分配:针对特定大小预分配,减少碎片但灵活性差。

TFLite Micro默认使用arena(内存池)分配器,其本质是一种顺序分配+偏移复用的策略,碎片模式与通用堆分配器有显著差异。

3.3.4 生命周期差异

短期对象与长期对象共存是碎片的温床。在TFLite Micro中,模型权重是长期存在的,而推理过程中的临时张量是短期的。如果短期张量在arena中穿插分配在长期张量之间,释放后留下的空洞就无法被后续的长期分配使用,形成永久性外部碎片。

风险提示:在TFLite Micro中,arena的碎片问题与通用堆不同——arena通常不进行空闲块合并(除非显式实现),因此外部碎片一旦形成,可能持续到arena被整体重置。这意味着“多次推理后碎片累积”是TFLite Micro必须面对的现实问题。

3.4 案例拆解:一个典型的碎片产生过程

为了直观理解碎片如何产生,我们模拟一个简化的TFLite Micro推理场景。假设arena大小为1024字节,初始全部空闲。

场景设定:一个包含3个算子的模型,推理过程中需要分配以下张量:

  1. 算子1:输入张量(128B),权重(256B),输出张量(128B)
  2. 算子2:输入复用算子1的输出,权重(64B),中间张量(192B),输出(128B)
  3. 算子3:输入复用算子2的输出,权重(32B),输出(64B)

分配过程(简化,忽略对齐):

初始:空闲[0-1023]
1. 分配算子1输入:占用[0-127],空闲[128-1023]
2. 分配算子1权重:占用[128-383],空闲[384-1023]
3. 分配算子1输出:占用[384-511],空闲[512-1023]
4. 算子1完成,释放输入[0-127]和权重[128-383],保留输出[384-511]
   空闲:[0-383]连续块 + [512-1023]连续块
5. 分配算子2权重:从[0-383]中取[0-63],空闲[64-383] + [512-1023]
6. 分配算子2中间张量:从[64-383]中取[64-255],空闲[256-383] + [512-1023]
7. 分配算子2输出:从[256-383]中取[256-383](正好),空闲[512-1023]
8. 算子2完成,释放权重[0-63]和中间张量[64-255],保留输出[256-383]
   空闲:[0-255] + [384-511] + [512-1023]
9. 分配算子3权重:从[0-255]取[0-31],空闲[32-255] + [384-511] + [512-1023]
10. 分配算子3输出:从[32-255]取[32-95],空闲[96-255] + [384-511] + [512-1023]

结果分析:此时arena中空闲块为[96-255](160B)、[384-511](128B)、[512-1023](512B)。如果此时有一个新请求需要200B的连续内存,虽然总空闲空间为160+128+512=800B,但最大的连续块只有512B,可以满足;但如果请求需要300B,则只有[512-1023]能满足。若请求需要600B,则分配失败——尽管总空闲空间充足。

复盘经验:

  • 碎片产生的关键节点是步骤4和步骤8——释放大块后,后续的小请求将其“切碎”。
  • 如果调整分配顺序(例如先分配大块),碎片程度会显著降低。
  • TFLite Micro的arena可以通过离线规划(在编译时确定所有张量的偏移)来完全避免运行时碎片。

3.5 常见误区与总结复盘

在理解内存碎片时,开发者常陷入以下误区:

误区一:认为碎片只影响大内存系统。实际上,MCU上的小内存系统对碎片更敏感——因为可用内存总量小,碎片比例稍高就可能导致分配失败。
误区二:混淆碎片与内存泄漏。碎片是“空间存在但不可用”,泄漏是“空间完全丢失”。TFLite Micro的arena通常不会泄漏,但碎片问题可能更严重。
误区三:认为对齐导致的内部碎片可以忽略。在TFLite Micro中,如果arena按16字节对齐,一个模型有100个张量,平均每个张量浪费7.5字节,总浪费就达750字节——在64KB RAM的MCU上,这接近1.2%的内存。

总结复盘:内存碎片是嵌入式系统内存管理的核心挑战之一。内部碎片源于分配粒度与请求大小的不匹配,外部碎片源于分配/释放模式的不对称。TFLite Micro的arena机制虽然避免了传统堆的许多问题,但其顺序分配和偏移复用策略也引入了独特的碎片模式。理解这些概念后,我们才能进一步分析TFLite Micro中“碎片假象”(即看起来碎片很多,但实际占用可控)与“实际占用”(即真实的内存消耗)之间的差异。

实践建议:在分析TFLite Micro内存问题时,建议先区分“内部碎片”和“外部碎片”——内部碎片可以通过调整对齐参数或使用紧凑打包来优化,外部碎片则需要通过调整张量分配顺序或使用arena复用策略来缓解。下一章将深入分析TFLite Micro的arena机制如何影响这两种碎片的实际表现。