作为练习,我重写了一些 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
}
}
发生了什么
我的过滤器
swift 过滤器
我所期待的
我期待类似的表现。我很困惑为什么我的过滤器函数调用本身会消耗更少的 CPU,而我的整体应用程序 CPU 却高出近 30%。
我的问题
如果我写了
filter
错误,请帮助我理解我的错误。否则请指出为什么 Swift 的 filter
与我的相比,CPU 负载降低了 30%。
最佳答案
好的,所以在阅读了所有发布的评论后,我决定也进行基准测试,这是我的结果。奇怪的是,内置 filter
似乎比自定义实现更糟糕。
TL; 博士; 由于您的函数很短,并且编译器可以访问它的源代码,因此编译器会内联函数调用,从而实现更多优化。
另一个考虑是作为您的 myFilter
声明不考虑异常抛出闭包,内置 filter
做。
添加 @inline(never)
, throws
和 rethrows
给您的 myFilter
声明,您将获得与内置 filter
类似的结果
研究成果
我用过 mach_absolute_time()
以获得准确的时间。我没有将结果转换为秒,因为我只是对比较感兴趣。测试在 Yosemite 10.10.5 和 Xcode 7.2 上运行。
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
分析:(来源:cristik-test.info)
myFilter
:Eureka !,我找到了答案。没有
myFilter
的踪迹call,意味着编译器内联了函数调用,从而实现了额外的优化,从而提高了性能。我添加了
@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 可能会对大型数组产生很大的影响。向数组添加新元素可能会导致数组需要增加其容量,这意味着分配新内存并复制当前状态的内容。
关于swift - 为什么我的过滤器版本与 Swifts 的表现如此不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34540874/