Improve memory usage and performance with Swift
Swift 进阶 1m

改善 Swift 的内存使用和性能

Improve memory usage and performance with Swift

2025年6月9日

在 Apple 官方观看视频

一句话判断

从 700 倍性能差距到丝滑——用 Instruments 定位性能瓶颈,然后用 InlineArraySpanOutputSpan 把 retain/release 和内存分配统统干掉。

这场 Session 讲了什么

Nate Cook 用一个 QOI 图片解析器做 demo,从一个实际的性能问题出发,手把手带你做五层优化:

  1. 算法修正Data.dropFirst() 实际上是 O(n) 复制,换成 popFirst() 就行。这个低级错误让解析时间从线性变成了二次。
  2. 消除多余分配:把 flatMap + prefix 链式调用重写为预分配 Data + 逐像素写入,从近百万次分配降到个位数。
  3. 消除 exclusivity 运行时检查:把 class 里的属性挪到 struct 里,swift_beginAccess 检查就消失了。
  4. InlineArray 替代固定大小 Array:Swift 6.2 新增的 InlineArray 用 value generics 把大小编译进类型,元素存在栈上,零引用计数。
  5. Span 替代 Data/ArrayRawSpanOutputSpan 是 non-escapable 类型,编译器保证生命周期安全,消除了 retain/release 开销。

值得深挖的点

  1. InlineArray 是 value generics 的首次实战应用InlineArray<64, RGBA> 把 64 个元素存在栈上,不需要 heap 分配,不需要 copy-on-write,不需要引用计数。适合固定大小、原地修改的场景。

  2. non-escapable 类型改变了性能游戏规则Span 系列类型用编译期生命周期保证替代了运行时引用计数。用在 tight loop 里效果巨大——demo 里 retain/release 各占 7% 的采样,换完直接归零。

  3. OutputSpan 用于渐进式写入Data(rawCapacity:) 提供一个 OutputSpan,你可以往里面 append 数据,不需要手动跟踪 offset,比 unsafe buffer pointer 安全,比 Data 高效。

  4. 全新的 Swift Binary Parsing 库。Apple 内部已经在用,基于这些新特性构建,提供安全的二进制格式解析工具。

代码片段

InlineArray 替代固定大小的 Array:

// 之前:堆分配 + 引用计数
var pixelCache: [RGBA] = Array(repeating: .zero, count: 64)

// 之后:栈上分配,零引用计数
var pixelCache: InlineArray<64, RGBA> = .init(repeating: .zero)

RawSpan 替代 Data 做解析,消除 retain/release:

// 之前:每次调用都有引用计数开销
func readByte(from data: inout Data) -> UInt8 {
    let byte = data.first!
    data = data.dropFirst()  // O(n) 复制!
    return byte
}

// 之后:RawSpan 是 non-escapable,零引用计数
func readByte(from span: inout RawSpan) -> UInt8 {
    let byte = span.unsafeLoad(as: UInt8.self)
    span = span.extracting(1...)
    return byte
}

最佳实践

  • 用 Instruments 的 Time Profiler 和 Allocations 两个工具配合看。先用 Time Profiler 找热点,再用 Allocations 看分配来源。开启 Invert Call Tree 能快速定位用户代码。
  • 对性能关键路径 profile 时,循环跑 50 次以上,给 Instruments 足够的采样数据。
  • 固定大小的数组一律考虑 InlineArray,特别是那些只做原地修改不做拷贝的场景。
  • 解析类代码优先用 RawSpan/OutputSpan,不要用 Data 做中间层。Data 的 copy-on-write 在 tight loop 里是性能杀手。
  • mx.compile(如果是 MLX 场景)或手动合并计算图节点,减少 GPU kernel launch 次数。

还有什么值得关注

  • Swift Binary Parsing 库已经开源,支持 ParserSpan、字节序处理、溢出安全计算等,适合做任何二进制格式解析。
  • UTF8Span 是专门做 Unicode 处理的 non-escapable 类型,做文本解析时可以用。
  • non-escapable 类型目前还在演进中,未来可能有更多标准库类型支持。
  • 参考 session 311 了解 Span 在 C/C++ 互操作中的应用。
Swift