UnsafeMutableBufferを使った記事を書いていて、ふと疑問に思った事があります。
UnsafeMutableBufferを使ったコードと、[]演算子を使ったコードはパフォーマンスがどの程度変わるのか?です。
この記事ではUnsafeMutableBufferと[]演算子のパフォーマンスを比較しました。
どのように比較するか
シンプルにUInt8の配列を確保し、全要素に順次アクセスで値を設定するという処理を、UnsafeMutableBufferを使った実装と[]演算子を使った実装で時間を計測します。
確保する容量を変更して、どの程度の容量でどの程度、時間が変わるのかを観測します。
テストコード
テストコードは次のようなコードです。
import Foundation
func fillSequentialNumber(buffer: UnsafeMutableBufferPointer<UInt8>, length: Int) {
var cur = buffer.startIndex
let end = buffer.startIndex.advanced(by: length)
while cur < end {
buffer[cur] = UInt8((cur - buffer.startIndex) % 255)
cur = cur.advanced(by: 1)
}
}
func fillSequalNumber(array: inout [UInt8]) {
for i in 0 ..< array.count {
array[i] = UInt8(i % 255)
}
}
func measureTime(proc: ()->()) -> TimeInterval {
let startDate = Date()
proc()
let time = Date().timeIntervalSince(startDate)
return time
}
func sequentialWriteTest(count: Int, label: String) {
var buf = [UInt8](repeating: 0, count: count)
let unsafeTime = measureTime {
buf.withUnsafeMutableBufferPointer { (ptr) -> Void in
fillSequentialNumber(buffer: ptr, length: count)
}
}
let arrayTime = measureTime {
fillSequalNumber(array: &buf)
}
print("\(label)")
print(" unsafe: \(unsafeTime) s")
print(" array: \(arrayTime) s")
}
sequentialWriteTest(count: 1024, label: "1KB")
sequentialWriteTest(count: 1024 * 1024, label: "1MB")
sequentialWriteTest(count: 1024 * 1024 * 10, label: "10MB")
sequentialWriteTest(count: 1024 * 1024 * 100, label: "100MB")
実行結果
実行環境
次のような動作環境で実行しました。
- Machine : MacBook Pro 2020 M1
- OS : macOS Big Sur 11.2.3
出力内容
コンソールに次のように出力されました。
1KB unsafe: 0.00014102458953857422 s array: 0.0006769895553588867 s 1MB unsafe: 0.08228003978729248 s array: 0.4330480098724365 s 10MB unsafe: 0.8178319931030273 s array: 4.373885989189148 s 100MB unsafe: 8.186002969741821 s array: 43.434290051460266 s
考察
1KBのときは、差はあれど、0.1msと0.6msなので無視できるレベルです。
1MBになると、82msと433msなので、ユーザー体験としては差を感じるレベルになります。
それ以上の容量になると、もはやプログレスバーインジケーターが必要になるレベルの差があります。
プログラムの中のちょっとした配列、要素数が数千個くらいまでならどちらでも書いても大丈夫ですが、それを超えてくると明らかにUnsafeMutableBufferを使った方がパフォーマンス的に有利です。この差はユーザー体験でもはっきりと現れてくると思います。
画像処理などをSwiftで実装する場合には、[]演算子を使うのは論外と言えます。可能ならば、Swiftで実装するよりもC/C++で実装してSwiftから呼び出すラッパーをObjective-Cでクラス化するか、ラッパーをC関数で実装して、Swiftでラッパークラスやユーティリティクラスを作るのが現実的だと思います。











