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

时间:2014-09-21 13:47:25

标签: performance generics 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
}

假设发布版本(-Os),您希望如何比较它们的性能?

我的期望是[Int] -> [Int]会比[AnyObject] -> [AnyObject]快得多......而且......会快几个数量级。

但是,我还希望[T] -> [T]的效果比[AnyObject] -> [AnyObject]好得多,几乎和[Int] -> [Int]一样快......对吗?

我发现这是错误的:即使[AnyObject] -> [AnyObject](甚至包括转回[Int])也比[T] -> [T]快5倍!这是令人失望的,因为泛型是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%
}

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

修改

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

func ==(lhs: (Int, Int), rhs: (Int, Int)) -> Bool {
    return lhs.0 == rhs.0 && lhs.1 == rhs.1
}

与之相比:

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

结果:

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%
    }
}

与之相比:

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%
    }
}

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

编辑2

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

编辑3

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

1 个答案:

答案 0 :(得分:6)

  

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

您应该将此因素纳入您的代码设计中。 Swift编译器正在快速发展,优化器正在不断发展。在早期版本的优化器上基于微基准更改编码实践是“过早优化”的最糟糕形式。

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

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


编辑:我还没有看过汇编程序输出(你应该),但我确实有几个理论说明为什么这可能是真的(如果它确实是真的;我也没有重现它)。 [AnyObject]可以替换为NSArray,其Array来自[AnyObject]。这是你永远不应该认为“a+a更快”的关键原因,这是基于一些不是真正代码的微基准。 [Int]的性能可能与其他一些操作的性能完全无关。

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

同样,你应该从不假设你知道优化器将要做什么而不测试至少与你关心的相似的实时代码(并且直到你有理由相信这是一个性能瓶颈)。编写“疯狂的代码因为速度更快”非常容易,实际上速度要慢得多,但仍然很疯狂。

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