为什么我的filter版本与Swifts的表现如此不同?

为什么我的filter版本与Swifts的表现如此不同?,swift,filter,instruments,higher-order-functions,Swift,Filter,Instruments,Higher Order Functions,作为练习,我重写了一些Swift的高阶函数,其中一个是.filter。我决定根据斯威夫特使用的仪器来衡量我的.filter版本,结果让我相当困惑 下面是我的过滤器版本,我承认可能不正确 extension Array { func myFilter(predicate: Element -> Bool) -> [Element] { var filteredArray = [Element]() for x in self where pred

作为练习,我重写了一些Swift的高阶函数,其中一个是
.filter
。我决定根据斯威夫特使用的仪器来衡量我的
.filter
版本,结果让我相当困惑

下面是我的过滤器版本,我承认可能不正确

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%

我的问题
如果我写的
过滤器
错误,请帮助我理解错误。否则,请指出为什么Swift的
过滤器
比我的减少了30%的CPU负载。

好的,所以在阅读了所有发布的评论之后,我决定也进行基准测试,下面是我的结果。奇怪的是,内置的
过滤器
的性能似乎比自定义实现差

TL;博士因为您的函数很短,而且编译器可以访问它的源代码,所以编译器会将函数调用内联,从而实现更多优化

另一个需要考虑的问题是,您的
myFilter
声明没有考虑异常抛出闭包,这是内置的
filter
所做的事情

@inline(never)
抛出和
重新抛出添加到
myFilter
声明中,您将获得与内置
过滤器类似的结果

研究成果 我使用
mach\u absolute\u 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
filter:         15966626
myFilter:       4013645
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
    }
}
然而,在
发行版中,
myFilter
filter
好得多:

filter:         370930078
myFilter:       479532958
filter:         15966626
myFilter:       4013645
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
    }
}
更奇怪的是,内置的
过滤器的精确副本(摘自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)")
回到
debug
模式,3种风格的
filter
提供以下输出:

filter:         343038057
myFilter:       429109866
originalFilter: 345482809
filter
originalFilter
给出了非常接近的结果。这让我觉得Xcode是针对Swifts stdlib的调试版本进行链接的。然而,当内置
发行版
时,Swifts stdlib的性能比
调试版
好3倍,这让我很困惑

所以下一步是分析。我点击
Cmd+I
,将采样间隔设置为40us,并对应用程序进行了两次分析:一次是在仅启用
filter
调用时,另一次是启用了
myFilter
。为了使堆栈跟踪尽可能干净,我删除了打印代码

内置
过滤器
评测:
(来源:)

myFilter

尤里卡!,我找到了答案。没有
myFilter
调用的跟踪,这意味着编译器将函数调用内联,从而启用额外的优化,从而提高性能

我将
@inline(never)
属性添加到
myFilter
,它的性能降低了

接下来,为了使它更接近内置过滤器,需要添加
throws
rethrows
声明,因为内置过滤器允许传递引发异常的闭包

令人惊讶的是(或不是),这就是我得到的:

filter: 11489238
myFilter: 6923719
myFilter not inlined: 9275967
my filter not inlined, with throws: 11956755
最终结论:编译器可以内联函数调用,加上缺乏对异常的支持,这是定制筛选方法性能更好的原因

以下代码给出的结果与内置的
过滤器非常相似:

filter:         370930078
myFilter:       479532958
filter:         15966626
myFilter:       4013645
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的
过滤器
性能应该更好,因为:

  • 它可以访问数组的内部状态,并且不强制执行枚举,这意味着至少少调用一个函数
  • 它可能会优化构建结果数组的方式
  • #1可能没有太大区别,因为函数调用不是很昂贵


    #另一方面,2可能会对大型阵列产生很大影响。将新元素添加到数组可能会导致数组需要增加其容量,这意味着分配新内存并复制当前状态的内容。

    我猜您的方法适用于数组,这意味着它处理循环中的所有数据,分配一个新数组并返回它-您的
    for…in
    循环然后迭代该数组(有效地进行第二次)
    SequenceType.filter()
    适用于
    SequenceType
    ,这只能保证它可以循环,从而将实现打开为生成器(即延迟计算)-这会导致数据只被枚举一次。@RichardSzalay您能碰巧提供一个更好的filter版本吗?就我而言,接受并投票可能是值得的。一件可能有帮助的事情是在闭包参数前面加上
    @noescape
    ,根据文档,这允许编译器“进行更积极的优化”。另外,您也可以直接查看。@MarcKhadpe谢谢您的帮助,我打算明天早上找到它。@DanBeaulieu我必须先熟悉Swift并获得Windows编译器;)我发表评论的原因是,我是根据我在其他语言/平台上的经验做出了一个假设。现在看来,(1)Swift确实使用枚举,(2)它没有做任何特殊的事情来优化它构建结果数组的方式(即它使用