一句话先给结论
是否是性能问题,取决于三件事:
- 是不是在热路径
- 是不是短生命周期 + 高频
- 是不是“本可避免”的分配
不是所有 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 / Dictionary | amortized 成本 |
| 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,看同一时间段
问三个问题:
- 这里是否发生分配?
- 是否与循环 / UI 更新同步?
- 是否可以通过结构调整避免?
Step 3:快速判断法(经验公式)
如果我把这段代码改成泛型 / inout / struct,本次分配是否会消失?
- 会消失 → 这是性能问题分配
- 不会消失 → 可能是合理成本
四、一个非常实用的判断表
| 特征 | 合理 | 性能问题 |
|---|---|---|
| 发生位置 | 初始化 / setup | for-loop / body / render |
| 生命周期 | 长 | 极短 |
| 分配频率 | 低 | 高 |
| 是否可避免 | 否 | 是 |
| 是否伴随 ARC 热点 | 少 | 多 |
| 改成泛型/inout 是否消失 | 否 | 是 |
核心结论
Allocations 不是“少即是好”,而是:
- 可预测
- 可摊销
- 不在热路径
才是好分配。