+对任何对象的Swift数组比对数组的T更快

mil*_*los 7 generics performance swift

鉴于以下三个简单的功能:

func twice_Array_of_Int(a: [Int]) -> [Int] {
    return a + a
}

func twice_Array_of_T<T>(a: [T]) -> [T] {
    return a + a
}

func twice_Array_of_Any(a: [AnyObject]) -> [AnyObject] {
    return a + a
}
Run Code Online (Sandbox Code Playgroud)

假设发布版本(-Os),你会如何期望他们的性能进行比较?

我的期望是,它[Int] -> [Int]会比[AnyObject] -> [AnyObject]...... 快得多......它的速度要快几个数量级.

但是,我也期望[T] -> [T]表现得比... 更好,[AnyObject] -> [AnyObject]并且差不多[Int] -> [Int]......对吧?

在这里,我证明是错的:即使[AnyObject] -> [AnyObject](甚至包括演员回来)也要[Int]快5倍[T] -> [T]!这是令人失望的,因为泛型是Swift最有前途的功能之一.

在其中一个WWDC视频中,Apple工程师提到他们正在本地实现泛型,即使用它们不会导致代码膨胀.这是否解释了表现不佳[T] -> [T]?如果他们只是在编译时消耗的通用功能,性能[T] -> [T][Int] -> [Int]应一直是相同的,对不对?

这是测试代码:

func testPerformance_twice_Array_of_Int() {
    let a = Array(1...100_000)
    self.measureBlock {
        let twice_a = twice_Array_of_Int(a)
    }
    // average: 0.000, relative standard deviation: 76.227%
}

func testPerformance_twice_Array_of_T() {
    let a = Array(1...100_000)
    self.measureBlock {
        let twice_a = twice_Array_of_T(a)
    }
    // measured [Time, seconds] average: 0.554, relative standard deviation: 7.846%
}

func testPerformance_twice_Array_of_Any() {
    let a = Array(1...100_000)
    self.measureBlock {
        let twice_a = twice_Array_of_Any(a) as [Int]
    }
    // average: 0.115, relative standard deviation: 8.303%

    // without the cast to [Int] = average: 0.039, relative standard deviation: 2.931%
}
Run Code Online (Sandbox Code Playgroud)

我很想听听您的意见,以及您计划如何将其纳入您的代码设计.

编辑

我刚刚做了一个更简单的测量,结果更令人吃惊:

func ==(lhs: (Int, Int), rhs: (Int, Int)) -> Bool {
    return lhs.0 == rhs.0 && lhs.1 == rhs.1
}
Run Code Online (Sandbox Code Playgroud)

相比:

func ==<T: Equatable>(lhs: (T, T), rhs: (T, T)) -> Bool {
    return lhs.0 == rhs.0 && lhs.1 == rhs.1
}
Run Code Online (Sandbox Code Playgroud)

结果:

func testPerformance_Equals_Tuple_Int() {
    let a = (2, 3)
    let b = (3, 2)
    XCTAssertFalse(a == b)
    let i = 1_000_000
    self.measureBlock() {
        for _ in 1...i {
            let c = a == b
        }
        // average: 0.002, relative standard deviation: 9.781%
    }
}
Run Code Online (Sandbox Code Playgroud)

相比:

func testPerformance_Equals_Tuple_T() {
    let a = (2, 3)
    let b = (3, 2)
    XCTAssertFalse(a == b)
    let i = 1_000_000
    self.measureBlock() {
        for _ in 1...i {
            let c = a == b
        }
        // average: 2.080, relative standard deviation: 5.118%
    }
}
Run Code Online (Sandbox Code Playgroud)

中缀函数的通用版本慢了1000多倍!

编辑2

8月21日,Austin Zheng在Swift语言用户组聚会(Chris Lattner作为特邀嘉宾)上发表了关于"Enums,Pattern Mating&Generics"的演讲.他说,Swift会发布针对常见类型优化的代码,但在运行时根据需要回退到其他类型的函数的本机泛型版本.请参阅:http://realm.io/news/swift-enums-pattern-matching-generics/(从32:00开始).

编辑3

随着Swift 2的推出,这对于更新来说已经过期了(只要我稍稍休息一下)......

Rob*_*ier 6

我很想听听您的意见,以及您计划如何将其纳入您的代码设计.

您不应该其纳入代码设计中.Swift编译器正在快速发展,优化器正在不断发展.在早期版本的优化器上基于微基准测试更改编码实践是"过早优化"的最糟糕形式.

代码清晰.正确的代码.当您看到性能问题时,请进行调查.没有程序比崩溃的程序慢.双方[Int][T]都更安全,更清晰,更容易的工作比[AnyObject](你必须不断的施放和验证).选择应该不难.如果你有一些实时代码演示了[T]Instruments中的问题,那么你应该调查其他选项(虽然我仍然会放在[AnyObject]底部;上面代码中的明显解决方案是编写一个特殊情况的重载,处理[Int]如果是真的更快).

由于您有一个有趣的测试用例,展示了通用和本机之间的惊人差异,因此打开雷达(bugreport.apple.com)是合适的.这样,当问题得到解决时,您清晰,正确的代码将获得免费的速度提升.


编辑:我还没有看过汇编输出(你应该),但我确实有几个理论为什么这可能是真的(如果它确实是真的;我也没有复制它).[AnyObject]这里可以用替换NSArray,它具有完全不同的性能特征Array.这是你不应该认为" [AnyObject]更快" 的关键原因,这是基于一些不是真正代码的微基准.其性能a+a可能与其他一些操作的性能完全无关.

关于[Int]vs [T],你可能会误解Swift如何处理泛型.Swift不为每种类型创建每个函数的全新版本.它创建了一个通用版本.该版本可能无法像特定类型版本那样优化所有内容.例如,在这种情况下,[T]版本可能会执行版本没有的内存管理[Int](我在这里完全猜测).优化器可以制作一个优化版本(这就是为什么你不应该尝试进行二次猜测),但它可能没有(这就是为什么你有时可能需要通过特殊的重载来帮助它).Swift Yeti有一篇很好的文章进一步解释.

同样,你应该永远相信你知道什么是优化会不会对现场测试代码至少是相似的,你关心什么(应该不是真的,甚至考虑它,直到你有一些理由相信这做的是一个性能瓶颈).编写"疯狂代码因为速度更快"非常容易,实际上速度要慢得多,但仍然很疯狂.

优化器知识就是力量,但如果您不确切知道何时使用它,它就是一种危险的力量.

  • 感谢您考虑的答案(+1).但是,我不确定建议"不要将其纳入您的代码设计"是多么有用.从我的问题可以清楚地看出,通用代码的"安全性"和"清晰度"促使问题开始.另外,我明确地引用了一位Apple工程师,他说他们正在本地实现泛型(即,正如你所说,"Swift不会为每种类型创建每个函数的全新版本"),所以我不知道为什么你觉得有必要重复这一点. (2认同)
  • 然而,我最不确定的是反对"疯狂代码"的一般论证.什么算作"疯狂"的时间变化和根据上下文.部分原因是我们倾向于"疯狂"的代码,这是很多改进的源泉.此外,隐藏在漂亮的API背后的一个非常好理解的"疯狂快速"例程将不会伤害任何人.而且,最后,不会提出诸如此类的问题可能对其他开发人员有帮助,否则他们可能很难理解为什么他们的代码不像他们预期的那样高效?斯威夫特只能从这种讨论中受益. (2认同)