惊人发现!Swift 循环性能对比,你用对了吗?
这里每天分享一个 iOS 的新知识,快来关注我吧
前言
在 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
循环和 reduce
比 while
循环慢这么多呢?
深入分析
for-in
循环分析
通过使用 Profiler Instruments 工具,我们可以更深入地了解 for-in
循环在底层的运作:
-
for
循环被转换为IndexingIterator
。 -
每次调用下一个索引时,都会触发
next()
函数(97% 的时间都花在调用next()
函数上)。 -
每次触发
next()
时,它会调用一系列协议方法(动态调度),这需要时间去查找协议表。
为什么 next()
函数使用动态调度呢?让我们看看 IndexingIterator
的实现细节:
public protocol Collection: Sequence {
...
}
public struct IndexingIterator<Elements: Collection> {
let _elements: Elements
}
extension IndexingIterator: IteratorProtocol, Sequence {
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-in
和 reduce
更好。
Swift 编译器探索
我们还可以使用 godbolt 工具来查看代码的编译方式:
在编译时,Swift 会将 for-in
转换为:
let range: Range<Int> = 0..<10000000
let iterator = range.makeIterator()
while iterator.next() {
...
}
Release 模式下的表现
在 Release 模式下,开启编译器优化后,结果如下:
-
for-in
Duration = 0.0063 秒 -
while
Duration = 0.0053 秒 -
reduce
Duration = 0.0075 秒
在编译器优化开启的情况下,Swift 不需要执行一堆动态调度。它只需管理一个计数器变量,增加它的值并执行求和操作,直到算出结果为止。🚀
学到什么?
-
Profiler Instrument 是一个调试应用程序性能的有用工具。
-
测量应用程序性能时,应在 Release 模式下进行。
-
了解了一些关于 Swift 编译器的知识。
总的来说,while
循环在 Debug 模式下表现更好,而在 Release 模式下,开启编译器优化后,各种循环的性能差异变得不明显,表明编译器优化对性能的影响很大。
这提醒我们,在进行性能优化时,必须考虑编译器优化的影响。
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!