作为练习,我重写了一些Swift的高阶函数,一个是.filter
.我决定.filter
使用乐器测量我对抗Swift的版本,我对结果感到困惑.
这是我的过滤器版本,我承认可能不正确.
extension Array { func myFilter(predicate: Element -> Bool) -> [Element] { var filteredArray = [Element]() for x in self where predicate(x) { filteredArray.append(x) } return filteredArray } }
我的过滤器
整体CPU消耗:85.7%
我的过滤器的消耗量:67.9%
斯威夫特的过滤器
整体CPU消耗:57.7%
我的过滤器的消耗量:70.9%
我期待类似的表现.我很困惑为什么我的过滤器函数调用本身会消耗更少的CPU,但我的整体应用程序CPU高出近30%.
如果我filter
写错了,请帮助我理解我的错误.否则请指出为什么Swift的filter
CPU负载比我的减少了30%.
好的,所以在阅读了所有发表的评论后,我决定也进行基准测试,这是我的结果.奇怪的是,内置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() 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的性能比它好3倍debug
,这让我很困惑.
所以下一步是分析.我点击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可能对大型阵列产生很大影响.将新元素附加到数组可能会导致数组需要增加其容量,这意味着分配新内存并复制当前状态的内容.