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 的幾何增加策略。

原文連接

相關文章
相關標籤/搜索