作为练习,我重写了一些Swift的高阶函数,一个是.filter
。我决定使用乐器测量我的版本extension Array {
func myFilter(predicate: Element -> Bool) -> [Element] {
var filteredArray = [Element]()
for x in self where predicate(x) {
filteredArray.append(x)
}
return filteredArray
}
}
对抗Swift,我对结果感到困惑。
以下是我的过滤器版本,我承认可能不正确。
filter
我的过滤器
Swift的过滤器
我期待类似的表现。我很困惑为什么我的过滤器函数调用本身会消耗更少的CPU,但我的整体应用程序CPU高出近30%。
如果我写错了filter
,请帮我理解我的错误。否则请指出为什么Swift的{{1}}比我的CPU负载减少了30%。
答案 0 :(得分:13)
好的,所以在阅读了所有发表的评论后,我决定也进行基准测试,这是我的结果。奇怪的是,内置filter
似乎比自定义实现更糟糕。
TL; DR; 因为您的函数很短,并且编译器可以访问它的源代码,所以编译器会内联函数调用,从而实现更多优化。
另一个考虑是因为你的myFilter
声明没有考虑异常抛出闭包,内置filter
所做的事情。
将@inline(never)
,throws
和rethrows
添加到您的myFilter
声明中,您将获得与内置filter
相似的结果
我使用mach_absolute_time()
来获取准确的时间。我没有将结果转换为秒,因为我只是对比较感兴趣。使用Xcode 7.2在Yosemite 10.10.5上进行测试。
import Darwin
extension Array {
func myFilter(@noescape predicate: Element -> Bool) -> [Element] {
var filteredArray = [Element]()
for x in self where predicate(x) {
filteredArray.append(x)
}
return filteredArray
}
}
let arr = [Int](1...1000000)
var start = mach_absolute_time()
let _ = arr.filter{ $0 % 2 == 0}
var end = mach_absolute_time()
print("filter: \(end-start)")
start = mach_absolute_time()
let _ = arr.myFilter{ $0 % 2 == 0}
end = mach_absolute_time()
print("myFilter: \(end-start)")
在debug
模式下,filter
比myFilter
更快:
filter: 370930078
myFilter: 479532958
但在release
中,myFilter
比filter
要好得多:
filter: 15966626
myFilter: 4013645
更奇怪的是,内置filter
的精确副本(取自Marc的评论)表现得比内置的更好。
extension Array {
func originalFilter(
@noescape includeElement: (Generator.Element) throws -> Bool
) rethrows -> [Generator.Element] {
var result = ContiguousArray<Generator.Element>()
var generator = generate()
while let element = generator.next() {
if try includeElement(element) {
result.append(element)
}
}
return Array(result)
}
}
start = mach_absolute_time()
let _ = arr.originalFilter{ $0 % 2 == 0}
end = mach_absolute_time()
print("originalFilter: \(end-start)")
使用上面的代码,我的基准测试应用程序提供以下输出:
filter: 13255199
myFilter: 3285821
originalFilter: 3309898
回到debug
模式,filter
的3种风格给出了这个输出:
filter: 343038057
myFilter: 429109866
originalFilter: 345482809
filter
和originalFilter
会给出非常接近的结果。这让我觉得Xcode与Swifts stdlib的调试版本相关联。但是当在release
中构建时,Swifts stdlib的执行速度比debug
好3倍,这让我很困惑。
所以下一步是分析。我点击了Cmd+I
,将采样间隔设置为40us,并对应用进行了两次分析:一次仅启用了filter
调用,另一次启用了myFilter
。我删除了打印代码,以使堆栈跟踪尽可能干净。
内置filter
分析:
myFilter
:
myFilter
调用的跟踪,这意味着编译器内联了函数调用,从而实现了额外的优化,从而提高了性能。
我将@inline(never)
属性添加到myFilter
,但性能下降了。
接下来,为了使其更接近内置过滤器,添加throws
和rethrows
声明,因为内置过滤器允许传递抛出异常的闭包。
令人惊讶(或不是),这就是我得到的:
filter: 11489238
myFilter: 6923719
myFilter not inlined: 9275967
my filter not inlined, with throws: 11956755
最终结论:编译器可以内联函数调用这一事实,加上缺乏对异常的支持,这对自定义过滤方法的性能提供了更好的帮助。
以下代码的结果与内置filter
非常相似:
extension Array {
@inline(never)
func myFilter(predicate: Element throws -> Bool) rethrows -> [Element] {
var filteredArray = [Element]()
for x in self where try predicate(x) {
filteredArray.append(x)
}
return filteredArray
}
}
Swift的filter
表现更好,因为:
#1可能没什么区别,因为函数调用不是很贵
另一方面,#2可能会对大型阵列产生很大影响。将新元素附加到数组可能会导致数组需要增加其容量,这意味着分配新内存并复制当前状态的内容。