Swiftで初期値を持つ配列を初期化するときのパフォーマンス比較（`Array(repeating:count:)` or `Array.reserveCapacity(_:)`）

## 概要

最近Swiftを書き始めてふと初期値をもつ配列を初期化するコードの最適解が知りたくなったので、思いついた以下の2+1つの方法について、パフォーマンスを計測して比較してみた

- `Array(repeating:count:)`したあと初期値をセットしていく
- 空の配列を作成後、`Array.reserveCapacity(_:)`してから初期値を追加していく
- （一応）空の配列を作成後、初期値を追加していく

## tl; dr

- 最適化オプションをつけないと、`Array.reserveCapacity(_:)`が速い
- 最適化オプションをつけると、`Array(repeating:count:)`が速い

## 実行環境

環境作成当時、最新の[Swift Docker Official Images](https://hub.docker.com/_/swift)

```bash
# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
# swiftc --version
Swift version 5.1.3 (swift-5.1.3-RELEASE)
Target: x86_64-unknown-linux-gnu
```

## ソースコード

```swift:BenchmarkInitializeSequence.swift
import Foundation

func repeating(n: Int) -> Double {
  let start = Date()
  var array = [Int](repeating: 0, count: n)
  for i in 0..<n {
    array[i] = i
  }
  return Date().timeIntervalSince(start)
}

func reserveCapacity(n: Int) -> Double {
  let start = Date()
  var array = [Int]()
  array.reserveCapacity(n)
  for i in 0..<n {
    array.append(i)
  }
  return Date().timeIntervalSince(start)
}

func append(n: Int) -> Double {
  let start = Date()
  var array = [Int]()
  for i in 0..<n {
    array.append(i)
  }
  return Date().timeIntervalSince(start)
}

func benchmarkInitializeSequence() {
  let testCases: [(n: Int, count: Int)] = [
    (10,100000),
    (100,100000),
    (1000,100000),
    (10000,10000),
    (100000,10000),
    (1000000,1000),
  ]

  for testCase in testCases {
    print("--- n = \(testCase.n)")

    // repeating
    var time = 0.0
    for _ in 0..<testCase.count {
      time += repeating(n: testCase.n)
    }
    print("repeating: \(time / Double(testCase.count))")

    // reserveCapacity
    time = 0.0
    for _ in 0..<testCase.count {
      time += reserveCapacity(n: testCase.n)
    }
    print("reserveCapacity: \(time / Double(testCase.count))")

    // append
    time = 0.0
    for _ in 0..<testCase.count {
      time += append(n: testCase.n)
    }
    print("append: \(time / Double(testCase.count))")
  }
}

benchmarkInitializeSequence()
```

計測方法についてはこちらの記事を参考にしました
[Swiftで処理時間を計測 - Qiita](https://qiita.com/osamu1203/items/9160f5f4951ebe205ea3)

## 結果 その1（コンパイルオプションの指定なし）

```bash
# swiftc BenchmarkInitializeSequence.swift
# ./BenchmarkInitializeSequence
--- n = 10
repeating: 5.331254005432129e-06
reserveCapacity: 5.172144174575806e-06
append: 5.743725299835205e-06
--- n = 100
repeating: 6.829563379287719e-06
reserveCapacity: 6.2863850593566896e-06
append: 7.3317515850067134e-06
--- n = 1000
repeating: 1.9573562145233155e-05
reserveCapacity: 1.7683861255645753e-05
append: 1.97959041595459e-05
--- n = 10000
repeating: 0.00014686577320098876
reserveCapacity: 0.00012827222347259521
append: 0.00017528159618377686
--- n = 100000
repeating: 0.0013892885684967042
reserveCapacity: 0.0012642447113990784
append: 0.001911275053024292
--- n = 1000000
repeating: 0.01432170021533966
reserveCapacity: 0.012672142624855042
append: 0.017902109384536742
```

## 結果 その2（最適化オプションあり）

```bash
# swiftc -O BenchmarkInitializeSequence.swift
# ./BenchmarkInitializeSequence
--- n = 10
repeating: 5.071830749511719e-06
reserveCapacity: 5.0783669948577884e-06
append: 5.610878467559814e-06
--- n = 100
repeating: 5.182200670242309e-06
reserveCapacity: 5.008577108383178e-06
append: 6.255366802215577e-06
--- n = 1000
repeating: 5.936795473098755e-06
reserveCapacity: 6.086642742156982e-06
append: 8.90161633491516e-06
--- n = 10000
repeating: 1.1534333229064942e-05
reserveCapacity: 1.8170416355133056e-05
append: 6.744261980056762e-05
--- n = 100000
repeating: 7.372238636016845e-05
reserveCapacity: 0.00014843966960906982
append: 0.0008369592905044555
--- n = 1000000
repeating: 0.001538015604019165
reserveCapacity: 0.001612482786178589
append: 0.007066493153572082
```

## 結論

- 最適化オプションをつけないと、`Array.reserveCapacity(_:)`を使うほうが`Array(repeating:count:)`を使うより速い
- 最適化オプションをつけると、`Array(repeating:count:)`を使うほうが`Array.reserveCapacity(_:)`を使うより速い
- 動的に長さを伸ばしていく方法（`Array.append`のみを使う）は遅い

## その他

最適化オプションをつけたときにどういったコードが吐き出されるかまでは確認していないので、どういった最適がされた結果、`Array(repeating:count:)`のほうが早くなったのかは分かっていません
Swiftについては何もわからんなので、有識者の方、各種コメントありましたらぜひいただければ幸いに思います