概要
最近Swiftを書き始めてふと初期値をもつ配列を初期化するコードの最適解が知りたくなったので、思いついた以下の2+1つの方法について、パフォーマンスを計測して比較してみた
-
Array(repeating:count:)
したあと初期値をセットしていく - 空の配列を作成後、
Array.reserveCapacity(_:)
してから初期値を追加していく - (一応)空の配列を作成後、初期値を追加していく
tl; dr
- 最適化オプションをつけないと、
Array.reserveCapacity(_:)
が速い - 最適化オプションをつけると、
Array(repeating:count:)
が速い
実行環境
環境作成当時、最新のSwift Docker Official Images
# 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
ソースコード
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
結果 その1(コンパイルオプションの指定なし)
# 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(最適化オプションあり)
# 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については何もわからんなので、有識者の方、各種コメントありましたらぜひいただければ幸いに思います