普通视图

发现新文章,点击刷新页面。
昨天以前首页

惊人发现!Swift 循环性能对比,你用对了吗?

作者 iOS新知
2025年6月25日 13:24

这里每天分享一个 iOS 的新知识,快来关注我吧

image.png

前言

在 Swift 编程中,我们经常需要使用循环来处理数据,那么你有没有想过哪些循环函数的性能最好?

今天我们随便挑几个循环函数来测一下性能。

for 循环、while 循环和高阶函数 reduce 的性能表现如何呢?让我们通过一个简单的性能测试来一探究竟。

性能测试代码

我们使用以下代码来测量不同循环结构的执行时间,如果你想测试其他的循环方法,也可以参考这个代码:

func processTime(_ title: String, closure: () -> ()) {
    let startTime = CFAbsoluteTimeGetCurrent()
    closure()
    let duration = CFAbsoluteTimeGetCurrent() - startTime
    print(title, "Duration = \(String(format : "%.4f", duration))")
}

processTime("for-in") {
  var sum = 0
  for i in 0..<10000000 {
    sum += i
  }
}

processTime("while") {
  var sum = 0
  var i = 0
  while i<10000000 {
    sum += i
    i += 1
  }
}
        
processTime("reduce") {
  let sum = (0..<10000000).reduce(0, +)
}

测试结果

在 Debug 模式下运行上面的代码,结果如下:

  • for-in Duration = 1.5586 秒

  • while Duration = 0.0115 秒

  • reduce Duration = 1.5766 秒

看到这个结果,我不禁好奇,为什么 for 循环和 reducewhile 循环慢这么多呢?

深入分析

for-in 循环分析

通过使用 Profiler Instruments 工具,我们可以更深入地了解 for-in 循环在底层的运作:

Image

  • for 循环被转换为 IndexingIterator

  • 每次调用下一个索引时,都会触发 next() 函数(97% 的时间都花在调用 next() 函数上)。

  • 每次触发 next() 时,它会调用一系列协议方法(动态调度),这需要时间去查找协议表。

为什么 next() 函数使用动态调度呢?让我们看看 IndexingIterator 的实现细节:

public protocol CollectionSequence {
    ...
}

public struct IndexingIterator<ElementsCollection> { 
    let _elements: Elements
}

extension IndexingIteratorIteratorProtocolSequence {
    public mutating func next() -> Elements.Element? {
        if _position == _elements.endIndex { return nil }
        let element = _elements[_position]
        _elements.formIndex(after: &_position)
        return element
    }
}

next() 函数中,我们使用了 _elements 属性。由于 _elements 可以是任何 Collection 协议类型,因此在编译时我们并不知道 _elements[_position]_elements.formIndex 指向哪个成员。因此,我们必须在运行时查看虚表并使用协议表。

相比之下,while 循环只需管理一个参数,不需要执行任何表查找。因此,while 循环的工作量更少,性能自然比 for-inreduce 更好。

Swift 编译器探索

我们还可以使用 godbolt 工具来查看代码的编译方式:

在编译时,Swift 会将 for-in 转换为:

let range: Range<Int> = 0..<10000000
let iterator = range.makeIterator()
while iterator.next() {
  ...
}

Image

Release 模式下的表现

在 Release 模式下,开启编译器优化后,结果如下:

  • for-in Duration = 0.0063 秒

  • while Duration = 0.0053 秒

  • reduce Duration = 0.0075 秒

在编译器优化开启的情况下,Swift 不需要执行一堆动态调度。它只需管理一个计数器变量,增加它的值并执行求和操作,直到算出结果为止。🚀

学到什么?

  1. Profiler Instrument 是一个调试应用程序性能的有用工具。

  2. 测量应用程序性能时,应在 Release 模式下进行。

  3. 了解了一些关于 Swift 编译器的知识。

总的来说,while 循环在 Debug 模式下表现更好,而在 Release 模式下,开启编译器优化后,各种循环的性能差异变得不明显,表明编译器优化对性能的影响很大。

这提醒我们,在进行性能优化时,必须考虑编译器优化的影响。

这里每天分享一个 iOS 的新知识,快来关注我吧

本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

❌
❌