Swift 中的 Array 性能比较: append vs reserveCapacity(译)

若是你往数组中添加不少元素,你可能会发现:使用reserveCapacity()函数提早告诉 Swift 你须要多大的长度,代码性能会更好。然而,对它的使用你须要格外当心,由于它也可能使你的代码变得很是很是慢。程序员

首先,让咱们看一下数组的存储策略。若是你建立一个包含四个元素的数组,Swift 将会分配足够的内存去存储这仅有的4个元素。因此,数组的 countcapacity 都是4。算法

如今,你想去添加第五个元素。但数组并无足够的长度去添加它,因此数组须要寻找一块足够存放五个元素的内存,而后将数组的4个元素拷贝过去,最后再拼接第五个元素。它的时间复杂度是O(n)。swift

为了不不断从新分配内存,Swift 对数组的容量采用了一种几何增长模式(a geometric allocation pattern)。这是一种很是好的方式,它成倍的增长数组的容量避免屡次从新分配内存的问题。当你在容量为4的数组中添加第五个元素的时候,Swift 将会将数组的长度增长为 8 。每当你超出数组的长度范围,它将会以3二、64等成倍的依次增长。数组

若是你知道你将要存储512个元素,你可使用reserveCapacity()函数来通知 Swift。而后 Swift 会马上分配一块能够存储512个元素的内存给数组,而不是建立一个小数组,再屡次从新分配内存。bash

示例:app

var randomNumbers = [Int]()
randomNumbers.reserveCapacity(512)

for _ in 1...512 {
    randomNumbers.append(Int.random(in: 1...10))
}
复制代码

因为reserveCapacity()的时间复杂度也是 O(n) ,因此你应该在数组为空的时候调用它。dom

可是这有一个很是重要的点:你须要肯定你的数组增加策略比 Swift 的好。记住, Swift 使用几何增加策略,因此调整数组尺寸的次数会随着数组容量的增长而减小,这就意味着它会将时间复杂度平摊为O(1)。函数

Tip:平摊(amortization): 是程序员选择用来描述算法随时间变化的行为的术语。虽然append()在不得不扩充数组的容量的时候时间复杂度是O(n) ;当你有足够的容量存储的时候,它的时间复杂度是O(1)。随着数组容量的增大,O(1) 操做将大大超过O(n)操做。所以咱们能够认为append()的时间复杂度是O(1)。性能

append()平摊为一个常量运行时间的时候,reserveCapacity()也是如此。以前的用法将会使你的代码变得更慢而不是更快。ui

例如,假设咱们想要追踪抽奖的幸运数字。咱们能够从一个空数组开始:

var allLuckyNumbers = [Int]()
复制代码

下一步,咱们能够编写一个可以选择10个数字的函数,以便咱们能够在本周的彩票中进行游戏。这个函数知道咱们将要生成10个数字,咱们可使用reserveCapacity()来肯定咱们的数组能够存放10个数字。

func pickLuckyNumbers() {
    let newSize = allLuckyNumbers.count + 10
    allLuckyNumbers.reserveCapacity(newSize)

    for _ in 1...10 {
        allLuckyNumbers.append(Int.random(in: 0...50))
    }
}
复制代码

目前为止还不错:reserveCapacity()的时间复杂度为O(n),而且他分配了足够的内存来存储。

可是人算不如天算,你可能提早想去生成整年的幸运数字。

for _ in 1...52 {
    pickLuckyNumbers()
}
复制代码

这个循环也是O(n),如今你摊上事了:循环里面嵌套循环,时间复杂度为O(n²)。

即便使用reserveCapacity()可让你的代码变快,可是在这种状况下它将使你的代码变慢:Swift 将会重复调整数组的容量去添加十多个元素。另外一方面,若是你移除了reserveCapacity(), Swift将恢复其几何增加策略,并最终分配比所需更多的容量。这将会比重复调整数组的容量快不少。

判断该使用的哪个的方法很简单:若是你调用reserveCapacity()一次,那么你就应该使用它,可是若是你可能会调用它屡次,那么你应该实现本身的增加策略或者使用 Swift 的几何增加策略。

原文连接

相关文章
相关标签/搜索