惊人发现!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-inDuration = 1.5586 秒 -
whileDuration = 0.0115 秒 -
reduceDuration = 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-inDuration = 0.0063 秒 -
whileDuration = 0.0053 秒 -
reduceDuration = 0.0075 秒
在编译器优化开启的情况下,Swift 不需要执行一堆动态调度。它只需管理一个计数器变量,增加它的值并执行求和操作,直到算出结果为止。🚀
学到什么?
-
Profiler Instrument 是一个调试应用程序性能的有用工具。
-
测量应用程序性能时,应在 Release 模式下进行。
-
了解了一些关于 Swift 编译器的知识。
总的来说,while 循环在 Debug 模式下表现更好,而在 Release 模式下,开启编译器优化后,各种循环的性能差异变得不明显,表明编译器优化对性能的影响很大。
这提醒我们,在进行性能优化时,必须考虑编译器优化的影响。
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!