mcpField
A
axiom-swift-performance

axiom-swift-performance

github.com/charleswiltgen开发1.2k201 次安装今天更新
代码仓库

关于 axiom-swift-performance

面向 Swift 开发者的性能优化指南,解释值语义、ARC、泛型和内存布局等语言层面的成本。适用于分析器已显示 Swift 代码瓶颈后,选择有依据的优化方式。

主要功能

  1. 01分析值类型与引用类型取舍
  2. 02讲解非拷贝类型和 COW
  3. 03涵盖 ARC 与闭包开销
  4. 04说明泛型特化和内联
  5. 05介绍集合与内存布局优化

适用场景

  1. 01优化分析器定位的 Swift 热点
  2. 02减少大型数据结构的复制
  3. 03降低高频路径中的 ARC 与分配开销
  4. 04为固定大小集合选择存储方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swift-performance

安装后在 Claude Code 中输入/axiom-swift-performance确认已加载。

详细介绍

Swift 性能优化

目的

核心原则:了解语言层面的性能特征——值语义、ARC 行为、泛型特化和内存布局——以此优化 Swift 代码,在不进行过早微优化的前提下编写快速、高效的代码。

Swift 版本:Swift 6.2+(适用于 InlineArray、Span、@concurrent) Xcode:16+ 平台:iOS 18+、macOS 15+

相关技能:

  • axiom-performance-profiling — 使用 Instruments 进行测量(请先做这一步!)
  • axiom-swiftui-performance — SwiftUI 专项优化
  • axiom-build-performance — 编译速度
  • axiom-swift-concurrency — 注重正确性的并发模式

适用场景

✅ 适合使用此技能的情况
  • 应用性能分析显示 Swift 代码是瓶颈(Time Profiler 热点)
  • 内存分配过多,或 retain/release 调用频繁
  • 实现性能关键型算法或数据结构
  • 编写对性能有要求的框架或库代码
  • 优化紧密循环或频繁调用的方法
  • 处理大型数据结构或集合
  • 代码评审发现性能反模式

快速决策树

Performance issue identified?
│
├─ Profiler shows excessive copying?
│  └─ → Part 1: Noncopyable Types
│  └─ → Part 2: Copy-on-Write
│
├─ Retain/release overhead in Time Profiler?
│  └─ → Part 4: ARC Optimization
│
├─ Generic code in hot path?
│  └─ → Part 5: Generics & Specialization
│
├─ Collection operations slow?
│  └─ → Part 7: Collection Performance
│
├─ Async/await overhead visible?
│  └─ → Part 8: Concurrency Performance
│
├─ Struct vs class decision?
│  └─ → Part 3: Value vs Reference
│
└─ Memory layout concerns?
   └─ → Part 9: Memory Layout

Swift 性能的四项原则

根据 WWDC 2024-10217,Swift 的底层性能特征可归纳为四个方面。每一项都对应本技能的一个部分。

原则开销技能涵盖内容
函数调用分派开销、优化障碍第 5 部分(泛型)、第 6 部分(内联)
内存分配栈与堆、分配频率第 3 部分(值与引用)、第 7 部分(集合)
内存布局缓存局部性、填充、连续性第 9 部分(内存布局)、第 11 部分(Span)
值复制COW 触发、保护性复制、ARC 调用第 1 部分(不可复制)、第 2 部分(COW)、第 4 部分(ARC)

了解哪项原则导致了瓶颈,有助于确定应该参考哪个部分。


第 1 部分:不可复制类型(~Copyable)

Swift 6.0+ 引入了不可复制类型,适用于需要避免隐式复制的性能关键型场景。

适用场景
  • 不应被复制的大型类型(文件句柄、GPU 缓冲区)
  • 具有所有权语义的类型(必须显式消耗)
  • 复制开销较高的性能关键型代码
基本模式
// Noncopyable type
struct FileHandle: ~Copyable {
    private let fd: Int32

    init(path: String) throws {
        self.fd = open(path, O_RDONLY)
        guard fd != -1 else { throw FileError.openFailed }
    }

    deinit {
        close(fd)
    }

    // Must explicitly consume
    consuming func close() {
        _ = consume self
    }
}

// Usage
func processFile() throws {
    let handle = try FileHandle(path: "/data.txt")
    // handle is automatically consumed at end of scope
    // Cannot accidentally copy handle
}
所有权注解
// consuming - takes ownership, caller cannot use after
func process(consuming data: [UInt8]) {
    // data is consumed
}

// borrowing - temporary access without ownership
func validate(borrowing data: [UInt8]) -> Bool {
    // data can still be used by caller
    return data.count > 0
}

// inout - mutable access
func modify(inout data: [UInt8]) {
    data.append(0)
}
性能影响
  • 消除隐式复制:编译器报错,而不是在运行时进行复制
  • 零成本抽象:性能与手动内存管理相同
  • 适用条件:类型复制开销较高(>64 字节),且很少发生复制

第 2 部分:写时复制(COW)

Swift 集合使用 COW 来高效共享内存。了解何时会发生复制对性能至关重要。

COW 的工作方式
var array1 = [1, 2, 3]  // Single allocation
var array2 = array1     // Share storage (no copy)
array2.append(4)        // Now copies (array1 modified array2)

如需自定义 COW 实现,请参阅下文的复制粘贴模式 1(COW 包装器)。

性能提示
// ❌ Accidental copy in loop
for i in 0..<array.count {
    array[i] = transform(array[i])  // Copy on first mutation if shared!
}

// ✅ Reserve capacity first (ensures unique)
array.reserveCapacity(array.count)
for i in 0..<array.count {
    array[i] = transform(array[i])
}

// ❌ Multiple mutations trigger multiple uniqueness checks
array.append(1)
array.append(2)
array.append(3)

// ✅ Single reservation
array.reserveCapacity(array.count + 3)
array.append(contentsOf: [1, 2, 3])
保护性复制

根据 WWDC 2024-10217,当 Swift 无法证明某个值不会通过共享引用被修改时,有时会插入保护性复制。

class DataStore {
    var items: [Item] = []  // COW type stored in class
}

func process(_ store: DataStore) {
    for item in store.items {
        // Swift may defensively copy `items` because:
        // 1. store.items is a class property (another reference could mutate it)
        // 2. The loop needs a stable snapshot
        handle(item)
    }
}

如何避免:先复制到局部变量中——显式复制一次,避免反复进行保护性复制:

func process(_ store: DataStore) {
    let items = store.items  // One copy
    for item in items {
        handle(item)  // No more defensive copies
    }
}

在性能分析器中:如果原本不应发生分配,保护性复制会表现为意料之外的 swift_retain/swift_release 配对,或 Array.__allocating_init 调用。


第 3 部分:值语义与引用语义

在 struct 和 class 之间进行选择,会对性能产生显著影响。

决策矩阵
因素使用 Struct使用 Class
大小≤ 64 字节> 64 字节或包含大型数据
标识不需要标识需要标识(===)
继承不需要需要继承
变更不频繁频繁原地更新
共享不需要共享必须跨作用域共享
小型 Struct(快速)
// ✅ Fast - fits in registers, no heap allocation
struct Point {
    var x: Double  // 8 bytes
    var y: Double  // 8 bytes
}  // Total: 16 bytes - excellent for struct

struct Color {
    var r, g, b, a: UInt8  // 4 bytes total - perfect for struct
}
大型结构体(较慢)
// ❌ Slow - excessive copying
struct HugeData {
    var buffer: [UInt8]  // 1MB
    var metadata: String
}

func process(_ data: HugeData) {  // Copies 1MB!
    // ...
}

// ✅ Use reference semantics for large data
final class HugeData {
    var buffer: [UInt8]
    var metadata: String
}

func process(_ data: HugeData) {  // Only copies pointer (8 bytes)
    // ...
}
灵活的间接存储

对于需要在外部表现为值语义、在内部使用引用存储的大型数据,请使用 COW Wrapper 模式——参见下文的复制粘贴模式 1。


第 4 部分:ARC 优化

自动引用计数会增加开销。应尽可能减少这种开销。

Weak 与 Unowned 的性能
class Parent {
    var child: Child?
}

class Child {
    // ❌ Weak adds overhead (optional, thread-safe zeroing)
    weak var parent: Parent?
}

// ✅ Unowned when you know lifetime guarantees
class Child {
    unowned let parent: Parent  // No overhead, crashes if parent deallocated
}

性能:unowned 比 weak 快约 2 倍(不需要原子操作)。

适用场景:子对象的生命周期短于父对象(有保证)。

闭包捕获优化
class DataProcessor {
    var data: [Int]

    // ❌ Captures self strongly, then uses weak - unnecessary weak overhead
    func process(completion: @escaping () -> Void) {
        DispatchQueue.global().async { [weak self] in
            guard let self else { return }
            self.data.forEach { print($0) }
            completion()
        }
    }

    // ✅ Capture only what you need
    func process(completion: @escaping () -> Void) {
        let data = self.data  // Copy value type
        DispatchQueue.global().async {
            data.forEach { print($0) }  // No self captured
            completion()
        }
    }
}
闭包捕获开销

根据 WWDC 2024-10217,闭包的性能特征会因其是否逃逸而异。

// Non-escaping closure — stack-allocated context, zero ARC overhead
func processItems(_ items: [Item], using transform: (Item) -> Result) -> [Result] {
    items.map(transform)  // Closure context lives on stack
}

// Escaping closure — heap-allocated context, ARC on every captured reference
func processItemsLater(_ items: [Item], transform: @escaping (Item) -> Result) {
    // Closure context heap-allocated as anonymous class instance
    // Each captured reference gets retain/release
    self.pending = { items.map(transform) }
}

这为何重要:@Sendable 闭包始终会逃逸,这意味着每个 Task 闭包都会在堆上分配其捕获上下文。

在热点路径中:优先使用非逃逸闭包。如果你在 Time Profiler 中看到闭包上下文调用了 swift_allocObject,请检查是否有可以改为非逃逸的逃逸闭包。

可观察对象的生命周期

根据 WWDC 2021-10216:对象的生命周期在最后一次使用时结束,而不是在右大括号处结束。

// ❌ Relying on observed lifetime is fragile
class Traveler {
    weak var account: Account?

    deinit {
        print("Deinitialized")  // May run BEFORE expected with ARC optimizations!
    }
}

func test() {
    let traveler = Traveler()
    let account = Account(traveler: traveler)
    // traveler's last use is above - may deallocate here!
    account.printSummary()  // weak reference may be nil!
}

// ✅ Explicitly extend lifetime when needed
func test() {
    let traveler = Traveler()
    let account = Account(traveler: traveler)

    withExtendedLifetime(traveler) {
        account.printSummary()  // traveler guaranteed to live
    }
}

对象生命周期可能随 Xcode 版本、Debug 与 Release 配置以及无关的代码更改而变化。开发期间启用“Optimize Object Lifetimes”(Xcode 13+),以便及早发现隐藏的生命周期错误。


第 5 部分:泛型与特化

泛型代码的性能可能很好,也可能很差,具体取决于是否进行了特化。

特化基础
// Generic function
func process<T>(_ value: T) {
    print(value)
}

// Calling with concrete type
process(42)  // Compiler specializes: process_Int(42)
process("hello")  // Compiler specializes: process_String("hello")
存在类型开销
protocol Drawable {
    func draw()
}

// ❌ Existential container - expensive (heap allocation, indirection)
func drawAll(shapes: [any Drawable]) {
    for shape in shapes {
        shape.draw()  // Dynamic dispatch through witness table
    }
}

// ✅ Generic with constraint - can specialize
func drawAll<T: Drawable>(shapes: [T]) {
    for shape in shapes {
        shape.draw()  // Static dispatch after specialization
    }
}

性能:泛型版本快约 10 倍(消除了见证表开销)。

存在类型容器开销

根据 WWDC 2016-416:any Protocol 使用一个 40 字节的存在类型容器(64 位系统上为 5 个字)。容器存储类型元数据和协议见证表(16 字节),以及一个 24 字节的内联值缓冲区。大小不超过 24 字节的类型会直接存储在缓冲区中(速度快,访问约需 5 ns);更大的类型则需要在堆上分配内存并通过指针间接访问(速度较慢,约需 15 ns)。some Protocol 可消除所有容器开销(约 2 ns)。

无法使用 some 时(异构集合需要使用 any):

  • 将类型大小缩减至不超过 24 字节——使符合协议的类型足够小,以便内联存储(3 个字:例如,Point { x, y, z: Double } 恰好适合)
  • 改用枚举分发——完全消除容器,但需以开放式扩展性换取性能:
// ❌ Existential: 40 bytes/element, witness table dispatch
let shapes: [any Drawable] = [circle, rect]

// ✅ Enum: value-sized, static dispatch via switch
enum Shape { case circle(Circle), rect(Rect) }
func draw(_ shape: Shape) {
    switch shape {
    case .circle(let c): c.draw()
    case .rect(let r): r.draw()
    }
}
  • 批量处理操作——分块处理,而不是逐个处理,以分摊每个元素的存在类型开销
  • 先测量——存在类型开销(每次访问约 10 ns)只在紧密循环中才重要;对于 UI 级别的代码,这种开销可以忽略
@_specialize 属性

当编译器不会自动进行特化时,可强制为常用类型进行特化:

@_specialize(where T == Int)
@_specialize(where T == String)
func process<T: Comparable>(_ value: T) -> T { value }
// Generates specialized versions + generic fallback

第 6 部分:内联

内联可以消除函数调用开销,但会增大代码体积。

何时进行内联
// ✅ Small, frequently called functions
@inlinable
public func fastAdd(_ a: Int, _ b: Int) -> Int {
    return a + b
}

// ❌ Large functions - code bloat
@inlinable  // Don't do this!
public func complexAlgorithm() {
    // 100 lines of code...
}
跨模块优化
// Framework code
public struct Point {
    public var x: Double
    public var y: Double

    // ✅ Inlinable for cross-module optimization
    @inlinable
    public func distance(to other: Point) -> Double {
        let dx = x - other.x
        let dy = y - other.y
        return sqrt(dx*dx + dy*dy)
    }
}

// Client code
let p1 = Point(x: 0, y: 0)
let p2 = Point(x: 3, y: 4)
let d = p1.distance(to: p2)  // Inlined across module boundary
@usableFromInline
// Internal helper that can be inlined
@usableFromInline
internal func helperFunction() { }

// Public API that uses it
@inlinable
public func publicAPI() {
    helperFunction()  // Can inline internal function
}

权衡:@inlinable 会公开实现细节,限制未来的优化空间。


第 7 部分:集合性能

选择合适的集合并正确使用很重要。

Array 与 ContiguousArray
// ❌ Array<T> - may use NSArray bridging (Swift/ObjC interop)
let array: Array<Int> = [1, 2, 3]

// ✅ ContiguousArray<T> - guaranteed contiguous memory (no bridging)
let array: ContiguousArray<Int> = [1, 2, 3]

适用 ContiguousArray 的场景:无需 ObjC 桥接(纯 Swift),速度快约 15%。

预留容量
// ❌ Multiple reallocations
var array: [Int] = []
for i in 0..<10000 {
    array.append(i)  // Reallocates ~14 times
}

// ✅ Single allocation
var array: [Int] = []
array.reserveCapacity(10000)
for i in 0..<10000 {
    array.append(i)  // No reallocations
}
Dictionary 哈希
struct BadKey: Hashable {
    var data: [Int]

    // ❌ Expensive hash (iterates entire array)
    func hash(into hasher: inout Hasher) {
        for element in data {
            hasher.combine(element)
        }
    }
}

struct GoodKey: Hashable {
    var id: UUID  // Fast hash
    var data: [Int]  // Not hashed

    // ✅ Hash only the unique identifier
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}
InlineArray(Swift 6.2)

直接存储在栈上的定长数组——无需堆分配,也没有 COW 开销。使用值泛型将大小编码到类型中。

// Traditional Array - heap allocated, COW overhead
var sprites: [Sprite] = Array(repeating: .default, count: 40)

// InlineArray - stack allocated, no COW (value generic syntax)
var sprites = InlineArray<40, Sprite>(repeating: .default)

符合的协议:RandomAccessCollection、MutableCollection、BitwiseCopyable、Sendable。支持 ~Copyable 元素类型。

何时使用 InlineArray:

  • 编译时已知固定大小
  • 对性能要求极高的路径(紧密循环、热点路径)
  • 想要完全避免堆分配
  • 小到中等大小(实用上限约为 1KB 栈使用量)

InlineArray 在栈上分配(不使用堆)、会立即复制(不采用 COW),并提供 .span/.mutableSpan 以实现零拷贝访问。请自行测量基准,以权衡其分配、复制和修改方面的表现与 Array 的差异。

复制语义警告:

// ❌ Unexpected: InlineArray copies eagerly
func processLarge(_ data: InlineArray<1000, UInt8>) {
    // Copies all 1000 bytes on call!
}

// ✅ Use Span to avoid copy
func processLarge(_ data: Span<UInt8>) {
    // Zero-copy view, no matter the size
}

// Best practice: Store InlineArray, pass Span
struct Buffer {
    var storage = InlineArray<1000, UInt8>(repeating: 0)

    func process() {
        helper(storage.span)  // Pass view, not copy
    }
}

何时不使用 InlineArray:

  • 大小可变(使用 Array)
  • 数据量大(栈使用量超过 1KB 有风险)
  • 经常按值传递(改用 Span)
  • 需要 COW 语义(使用 Array)
惰性序列
// ❌ Eager evaluation - processes entire array
let result = array
    .map { expensive($0) }
    .filter { $0 > 0 }
    .first  // Only need first element!

// ✅ Lazy evaluation - stops at first match
let result = array
    .lazy
    .map { expensive($0) }
    .filter { $0 > 0 }
    .first  // Only evaluates until first match

第 8 部分:并发性能

async/await 和 actor 会增加开销。请酌情使用。

Actor 隔离开销
actor Counter {
    private var value = 0

    // ❌ Actor call overhead for simple operation
    func increment() {
        value += 1
    }
}

// Calling from different isolation domain
for _ in 0..<10000 {
    await counter.increment()  // 10,000 actor hops!
}

// ✅ Batch operations to reduce actor overhead
actor Counter {
    private var value = 0

    func incrementBatch(_ count: Int) {
        value += count
    }
}

await counter.incrementBatch(10000)  // Single actor hop
Async 开销

每次 async 挂起的开销约为 20-30μs。同步操作应保持同步——如果函数不需要 await,就不要将其标记为 async。

Task 创建开销
// ❌ Creating task per item (~100μs overhead each)
for item in items {
    Task {
        await process(item)
    }
}

// ✅ Single task for batch
Task {
    for item in items {
        await process(item)
    }
}

// ✅ Or use TaskGroup for parallelism
await withTaskGroup(of: Void.self) { group in
    for item in items {
        group.addTask {
            await process(item)
        }
    }
}
@concurrent 属性(Swift 6.2)
// Force background execution
@concurrent
func expensiveComputation() -> Int {
    // Always runs on background thread, even if called from MainActor
    return complexCalculation()
}

// Safe to call from main actor without blocking
@MainActor
func updateUI() async {
    let result = await expensiveComputation()  // Guaranteed off main thread
    label.text = "\(result)"
}

如需了解 nonisolated 性能模式和 actor 隔离的详细指导,请参阅 axiom-swift-concurrency。


第 9 部分:内存布局

了解内存布局有助于优化缓存性能并减少分配。

结构体填充
// ❌ Poor layout (24 bytes due to padding)
struct BadLayout {
    var a: Bool    // 1 byte + 7 padding
    var b: Int64   // 8 bytes
    var c: Bool    // 1 byte + 7 padding
}
print(MemoryLayout<BadLayout>.size)  // 24 bytes

// ✅ Optimized layout (16 bytes)
struct GoodLayout {
    var b: Int64   // 8 bytes
    var a: Bool    // 1 byte
    var c: Bool    // 1 byte + 6 padding
}
print(MemoryLayout<GoodLayout>.size)  // 16 bytes
对齐
// Query alignment
print(MemoryLayout<Double>.alignment)  // 8
print(MemoryLayout<Int32>.alignment)   // 4

// Structs align to largest member
struct Mixed {
    var int32: Int32   // 4 bytes, 4-byte aligned
    var double: Double // 8 bytes, 8-byte aligned
}
print(MemoryLayout<Mixed>.alignment)  // 8 (largest member)
缓存友好的数据结构
// ❌ Poor cache locality
struct PointerBased {
    var next: UnsafeMutablePointer<Node>?  // Pointer chasing
}

// ✅ Array-based for cache locality
struct ArrayBased {
    var data: ContiguousArray<Int>  // Contiguous memory
}

// Array iteration ~10x faster due to cache prefetching
独占性检查

根据 WWDC 2025-312:当编译器无法通过静态分析证明内存安全时,运行时独占性检查(swift_beginAccess/swift_endAccess)会出现在 Time Profiler 中。

它们是什么:Swift 会确保对同一变量的多个访问不会重叠,只要其中一个是写入。对于结构体属性,这项检查在编译时进行;对于类的存储属性,则会插入运行时检查。

如何识别:在 Time Profiler 或 Processor Trace 火焰图中查找 swift_beginAccess 和 swift_endAccess。

// ❌ Class properties require runtime exclusivity checks
class Parser {
    var state: ParserState
    var cache: [Int: Pixel]

    func parse() {
        state.advance()         // swift_beginAccess / swift_endAccess
        cache[key] = pixel      // swift_beginAccess / swift_endAccess
    }
}

// ✅ Struct properties checked at compile time — zero runtime cost
struct Parser {
    var state: ParserState
    var cache: InlineArray<64, Pixel>

    mutating func parse() {
        state.advance()         // No runtime check
        cache[key] = pixel      // No runtime check
    }
}

实际影响:在 WWDC 2025-312 的 QOI 图像解析器中,将属性从类移到结构体后,所有运行时独占性检查都被消除,这促成了可测量的性能提升;该优化是总计超过 700 倍提升的一部分。


第 10 部分:类型化抛出(Swift 6)

通过避免存在类型开销,类型化抛出可能比非类型化抛出更快。

非类型化与类型化对比
// Untyped - existential container for error
func fetchData() throws -> Data {
    // Can throw any Error
    throw NetworkError.timeout
}

// Typed - concrete error type
func fetchData() throws(NetworkError) -> Data {
    // Can only throw NetworkError
    throw NetworkError.timeout
}
性能影响
// Measure with tight loop
func untypedThrows() throws -> Int {
    throw GenericError.failed
}

func typedThrows() throws(GenericError) -> Int {
    throw GenericError.failed
}

// Benchmark: typed ~5-10% faster (no existential overhead)
何时使用
  • 类型化:具有明确定义错误类型的库代码、热点路径
  • 非类型化:应用代码、编译时无法确定错误类型的场景

第 11 部分:Span 类型

Swift 6.2+ 引入了 Span——一种不可逃逸、不拥有内存的视图,可安全、高效地访问连续数据。

什么是 Span?

Span 是 UnsafeBufferPointer 的现代替代方案,具有以下特性:

  • 空间安全:带边界检查的操作可防止越界访问
  • 时间安全:生命周期继承自来源,防止释放后使用
  • 零开销:无需堆分配,也无需引用计数
  • 不可逃逸:其生命周期不会超出所引用数据的生命周期
// Traditional unsafe approach
func processUnsafe(_ data: UnsafeMutableBufferPointer<UInt8>) {
    data[100] = 0  // Crashes if out of bounds!
}

// Safe Span approach
func processSafe(_ data: MutableSpan<UInt8>) {
    data[100] = 0  // Traps with clear error if out of bounds
}
何时使用 Span、Array 或 UnsafeBufferPointer
使用场景建议
拥有数据Array(完全拥有,COW)
临时读取视图Span(安全、快速)
临时写入视图MutableSpan(安全、快速)
C 互操作、性能要求极高RawSpan(无类型字节)
不安全的性能优化UnsafeBufferPointer(旧版,避免使用)
Span 基本用法
let array = [1, 2, 3, 4, 5]
let span = array.span        // Read-only view
print(span[0])               // Subscript access
for element in span { }      // Safe iteration
let slice = span[1..<3]      // Span slice, no copy
使用 MutableSpan 进行修改
var array = [10, 20, 30, 40, 50]
let mutableSpan = array.mutableSpan
mutableSpan[0] = 100  // Modifies array in-place, bounds-checked
用于未类型化字节的 RawSpan
func parsePacket(_ data: RawSpan) -> PacketHeader? {
    guard data.count >= MemoryLayout<PacketHeader>.size else { return nil }
    // Safe byte-level access via subscript
    return PacketHeader(version: data[0], flags: data[1],
        length: UInt16(data[3]) << 8 | UInt16(data[2]))
}

let header = parsePacket(bytes.rawSpan)  // .rawSpan on any [UInt8]

所有 Swift 6.2 集合都提供 .span 和 .mutableSpan 属性,包括 Array、ContiguousArray 和 UnsafeBufferPointer(迁移路径)。Span 的访问速度与 UnsafeBufferPointer 相当(约 2ns),并带有边界检查。

非逃逸生命周期安全性

Span 的生命周期与其源绑定。编译器会阻止从函数返回 Span,避免源被释放——而 UnsafeBufferPointer 则会悄无声息地允许这种错误。

func dangerousSpan() -> Span<Int> {
    let array = [1, 2, 3]
    return array.span  // ❌ Error: Cannot return non-escapable value
}

InlineArray 也提供 .span/.mutableSpan——有关 InlineArray 的用法和通过 Span 避免复制,请参阅第 7 部分。

从 UnsafeBufferPointer 迁移
// ❌ Old: unsafe, no bounds checking
func parseLegacy(_ buffer: UnsafeBufferPointer<UInt8>) -> Header {
    Header(magic: buffer[0], version: buffer[1])  // Silent OOB crash
}

// ✅ New: safe, bounds-checked, same performance
func parseModern(_ span: Span<UInt8>) -> Header {
    Header(magic: span[0], version: span[1])  // Traps on OOB
}

// Bridge: existing UnsafeBufferPointer → Span
let span = buffer.span  // Wrap unsafe in safe span
parseModern(span)
OutputSpan——安全初始化

OutputSpan/OutputRawSpan 可替代 UnsafeMutableBufferPointer,用于初始化新集合而无需中间分配。

// Binary serialization: write header bytes safely
@lifetime(&output)
func writeHeader(to output: inout OutputRawSpan) {
    output.append(0x01)       // version
    output.append(0x00)       // flags
    output.append(UInt16(42)) // length (type-safe)
}

可用于构建字节数组、二进制序列化数据和图像像素数据。Apple 的开源库 Swift Binary Parsing 完全基于 Span 类型构建。

不适合使用 Span 的情况
  • 所有权:Span 无法存储在结构体/类中——拥有数据时请使用 Array,并通过计算属性提供 .span 访问
  • 返回值:Span 不可逃逸——请在作用域内处理,并返回拥有所有权的数据
  • 长生命周期引用:Span 的生命周期与源绑定——如果数据必须超出当前作用域的生命周期,请使用 Array

可复制粘贴的模式

模式 1:COW 封装类型
final class Storage<T> {
    var value: T
    init(_ value: T) { self.value = value }
}

struct COWWrapper<T> {
    private var storage: Storage<T>

    init(_ value: T) {
        storage = Storage(value)
    }

    var value: T {
        get { storage.value }
        set {
            if !isKnownUniquelyReferenced(&storage) {
                storage = Storage(newValue)
            } else {
                storage.value = newValue
            }
        }
    }
}
模式 2:性能关键循环
func processLargeArray(_ input: [Int]) -> [Int] {
    var result = ContiguousArray<Int>()
    result.reserveCapacity(input.count)

    for element in input {
        result.append(transform(element))
    }

    return Array(result)
}
模式 3:内联缓存查找
private var cache: [Key: Value] = [:]

@inlinable
func getCached(_ key: Key) -> Value? {
    return cache[key]  // Inlined across modules
}

反模式

反模式问题修正方法
过早优化没有性能分析数据,却使用复杂的 COW/ContiguousArray从简单方案开始,进行性能分析,再优化关键部分
到处使用 weak每个代理上都使用 weak(带来原子操作开销)生命周期有保证时使用 unowned(参见第 4 部分)
所有情况都用 Actor对简单计数器使用 Actor 隔离(约 100μs/次调用)对简单的同步数据使用无锁原子操作(ManagedAtomic)

代码审查清单

内存管理
  • 大型结构体(>64 字节)使用间接存储或改用类
  • COW 类型在修改前使用 isKnownUniquelyReferenced
  • 已知集合大小时使用 reserveCapacity
  • 仅在需要时使用弱引用(安全时优先使用 unowned)
泛型
  • 在可能的情况下,协议类型使用 some 而非 any
  • 热路径使用具体类型或 @_specialize
  • 泛型约束应尽可能具体
集合
  • 纯 Swift 代码使用 ContiguousArray 而非 Array
  • Dictionary 键具有高效的 hash(into:) 实现
  • 短路操作使用惰性求值
并发
  • 同步操作不使用 async
  • 尽可能批量处理 Actor 调用
  • 尽量减少 Task 创建(使用 TaskGroup)
  • CPU 密集型工作使用 @concurrent(Swift 6.2)
优化
  • 优化前已有性能分析数据
  • 仅对小型且频繁调用的函数进行内联
  • 针对缓存局部性优化内存布局(大型结构体)

压力情境

情境 1:“赶紧让它快起来,我们明天就要发布”

压力来源:经理在性能分析器中看到“速度慢”,要求立即采取行动。

警示信号:

  • 没有基线测量数据
  • 没有显示热点的 Time Profiler 数据
  • 没有设定目标,只要求“全面提速”

耗时对比:

  • 过早优化:耗时 2 天,没有可测量的提升
  • 基于性能分析的优化:2 小时进行性能分析 + 4 小时修复实际瓶颈 = 提速 40%

如何以专业方式提出异议:

"I want to optimize effectively. Let me spend 30 minutes with Instruments
to find the actual bottleneck. This prevents wasting time on code that's
not the problem. I've seen this save days of work."
场景 2:“到处使用 actor 来保证线程安全”

压力:团队采用 Swift 6,决定“所有东西都应该是 actor”。

危险信号:

  • 为简单的值类型使用 actor
  • 仅用于同步操作的 actor
  • 紧密循环中的异步开销

时间成本对比:

  • 到处使用 actor:每次操作有 100μs 的开销,UI 卡顿
  • 采用适当的隔离:开销为 10μs,保持流畅的 60fps

如何以专业的方式提出异议:

"Actors are great for isolation, but they add overhead. For this simple
counter, lock-free atomics are 10x faster. Let's use actors where we need
them—shared mutable state—and avoid them for pure value types."
场景 3:“为了速度,将所有内容内联”

压力:有人读到内联速度更快,于是将所有内容都标记为 @inlinable。

危险信号:

  • 大型函数被标记为 @inlinable
  • 暴露内部实现细节
  • 二进制文件大小增加 50%

时间成本对比:

  • 将所有内容内联:代码膨胀,应用启动变慢(3s → 5s)
  • 选择性内联:启动迅速,实际热点得到优化

如何以专业的方式提出异议:

"Inlining trades code size for speed. The compiler already inlines when
beneficial. Manual @inlinable should be for small, frequently called
functions. Let's profile and inline the 3 actual hotspots, not everything."

真实案例

示例 1:图像处理管线

问题:处理 1000 张图像需要 30 秒。

调查:

// Original code
func processImages(_ images: [UIImage]) -> [ProcessedImage] {
    var results: [ProcessedImage] = []
    for image in images {
        results.append(expensiveProcess(image))  // Reallocations!
    }
    return results
}

解决方案:

func processImages(_ images: [UIImage]) -> [ProcessedImage] {
    var results = ContiguousArray<ProcessedImage>()
    results.reserveCapacity(images.count)  // Single allocation

    for image in images {
        results.append(expensiveProcess(image))
    }

    return Array(results)
}

结果:通过消除重新分配,将耗时从 30s 降至 8s(快 73%)。

示例 2:泛型特化

问题:基于协议的渲染速度很慢。

调查:

// Original - existential overhead
func render(shapes: [any Shape]) {
    for shape in shapes {
        shape.draw()  // Dynamic dispatch
    }
}

解决方案:

// Specialized generic
func render<S: Shape>(shapes: [S]) {
    for shape in shapes {
        shape.draw()  // Static dispatch after specialization
    }
}

// Or use @_specialize
@_specialize(where S == Circle)
@_specialize(where S == Rectangle)
func render<S: Shape>(shapes: [S]) { }

结果:通过消除见证表开销,将耗时从 100ms 降至 10ms(快 10 倍)。


资源

WWDC:2025-312、2024-10217、2024-10170、2021-10216、2016-416

文档:/swift/inlinearray、/swift/span、/swift/outputspan

技能:axiom-performance-profiling、axiom-swift-concurrency、axiom-swiftui-performance


评论

支持 Markdown