swift - 为什么我的过滤器版本与 Swifts 的表现如此不同?

标签 swift filter instruments higher-order-functions

作为练习,我重写了一些 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%

  • enter image description here
    enter image description here

    swift 过滤器
  • 整体 CPU 消耗:57.7%
  • 我的过滤器消耗:70.9%

  • enter image description here
    enter image description here

    我所期待的

    我期待类似的表现。我很困惑为什么我的过滤器函数调用本身会消耗更少的 CPU,而我的整体应用程序 CPU 却高出近 30%。

    我的问题

    如果我写了 filter错误,请帮助我理解我的错误。否则请指出为什么 Swift 的 filter与我的相比,CPU 负载降低了 30%。

    最佳答案

    好的,所以在阅读了所有发布的评论后,我决定也进行基准测试,这是我的结果。奇怪的是,内置 filter似乎比自定义实现更糟糕。

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

    另一个考虑是作为您的 myFilter声明不考虑异常抛出闭包,内置 filter做。

    添加 @inline(never) , throwsrethrows给您的 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模式,filtermyFilter 快:
    filter:         370930078
    myFilter:       479532958
    

    release然而,myFilterfilter好多了:
    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
    
    filteroriginalFilter给出非常接近的结果。这让我认为 Xcode 正在链接到 Swifts stdlib 的调试版本。但是,当在 release 中构建时, Swifts stdlib 的性能是 debug 的 3 倍,这让我很困惑。

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

    内置filter分析:
    build in filter time profiling
    (来源:cristik-test.info)
    myFilter :
    myFilter time profiling

    Eureka !,我找到了答案。没有 myFilter 的踪迹call,意味着编译器内联了函数调用,从而实现了额外的优化,从而提高了性能。

    我添加了 @inline(never)归因于 myFilter ,而且性能下降。

    接下来,为了使其更接近内置过滤器,添加 throwsrethrows声明,因为内置过滤器允许传递抛出异常的闭包。

    惊喜(或不惊喜),这就是我得到的:
    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/

    相关文章:

    extjs - 如何过滤多个extjs网格列?

    javascript - 无论单击表中的哪个项目,都将该值放在隐藏输入上

    ios - 用户界面自动化 : Any way to dismiss "Would Like To Use Your Current Location" alert?

    objective-c - xcode 中泄漏仪器的准确性

    xcode - 我如何阅读仪器?

    swift - 为什么我不能将字符串附加到 NSURL?

    javascript - 如何创建一个多重过滤功能来过滤掉多个属性?

    swift - 从 MTKView 创建的 UIImage 导致颜色/不透明度差异

    swift - 如何将 nil 值添加到 Swift 字典?

    ios - 半透明导航栏覆盖推送 View Controller ?