5-22.【性能分析与优化】Allocations 工具中如何区分“合理分配”和“性能问题分配”?

0 阅读3分钟

一句话先给结论

是否是性能问题,取决于三件事:

  1. 是不是在热路径
  2. 是不是短生命周期 + 高频
  3. 是不是“本可避免”的分配

不是所有 heap allocation 都是坏的,坏的是:可避免 + 高频 + 在循环里


一、Allocations 里我主要看哪几个维度?

1️⃣ 分配频率(Rate)比总量重要

  • ❌ 误区:

    “分配很多内存 = 性能问题”

  • ✅ 正确关注点:

    每秒分配多少次?是否随时间线性增长?

判断经验值(非绝对):

情况通常是 OK 的
启动阶段一次性大量分配合理
View 初始化时分配合理
每帧 / 每次滚动都分配🚨 高危
for-loop 中持续分配🚨 高危

👉 Allocations 面板里:

  • Allocations / sec
  • 看时间轴是否“锯齿状持续上升”

2️⃣ 生命周期:短命对象最危险

Allocations 里有一个非常重要但常被忽略的视角:

Heapshot / Lifetime

危险模式(性能问题)

  • 对象:

    • 分配
    • 很快释放
    • 再分配
    • 再释放
  • 表现:

    • retain / release 很多
    • cache 命中率低
    • CPU 忙着做 ARC

典型来源

  • closure
  • protocol existential boxing
  • 临时 Array / Dictionary
  • SwiftUI body 中生成对象

3️⃣ 分配发生在哪里(调用栈比类型更重要)

我会优先看:

  • Call Tree
  • 而不是对象类型本身

🚨 性能问题分配

for-loop
 └── compute()
     └── map / filter / reduce
         └── allocate Array

或:

scroll / layout / body
 └── closure
     └── existential boxing
         └── heap allocation

✅ 合理分配

init / viewDidLoad
 └── setup()
     └── allocate model / cache

二、Swift 里「合理分配 vs 性能问题分配」的典型对照

✅ 合理分配(不用焦虑)

场景为什么合理
class 实例长期存在ARC 成本可摊销
一次性创建大 Array / Dictionaryamortized 成本
cache / pool 对象用空间换时间
SwiftUI View struct 本身轻量值类型

🚨 性能问题分配(重点盯)

1️⃣ 协议存在类型(existential)导致的 boxing

let x: P = MyStruct() // heap box
  • struct 被放进 existential container

  • Allocations 中表现为:

    • 小但极高频的分配
    • 常伴随 swift_retain / release

👉 高频热路径中几乎必杀


2️⃣ 循环中的临时集合

for _ in 0..<10000 {
    let tmp = array.filter { $0 > 0 }
}
  • 每轮都新分配 Array
  • Allocations:大量短命 Array

✅ 优化:

  • 复用 buffer
  • inout
  • 手写循环

3️⃣ closure 捕获导致的 heap escape

values.forEach { v in
    self.process(v)   // self 被 retain
}
  • closure 对象 + retain/release
  • Allocations 中常和 ARC 热点同时出现

4️⃣ CoW 触发的隐式分配

var a = bigArray
for i in 0..<a.count {
    a[i] += 1
}
  • CoW buffer copy
  • Allocations:突然出现“大块内存复制”

三、我实际用 Allocations 的工作流

Step 1:先用 Time Profiler 找热路径

  • 哪段代码在烧 CPU

Step 2:切到 Allocations,看同一时间段

问三个问题:

  1. 这里是否发生分配?
  2. 是否与循环 / UI 更新同步?
  3. 是否可以通过结构调整避免?

Step 3:快速判断法(经验公式)

如果我把这段代码改成泛型 / inout / struct,本次分配是否会消失?

  • 会消失 → 这是性能问题分配
  • 不会消失 → 可能是合理成本

四、一个非常实用的判断表

特征合理性能问题
发生位置初始化 / setupfor-loop / body / render
生命周期极短
分配频率
是否可避免
是否伴随 ARC 热点
改成泛型/inout 是否消失

核心结论

Allocations 不是“少即是好”,而是:

  • 可预测
  • 可摊销
  • 不在热路径

才是好分配。