Array
タイプのSwift標準ライブラリsort()
関数を選択してプローブしています。驚いたことに、すでにソートされている関数ではパフォーマンスが低いことに気付きました。データ。
シャッフルされたInt
の配列の並べ替えは、同じ配列が既に並べ替えられている場合に並べ替えるよりも5倍速いようです。シャッフルされたオブジェクトの配列の並べ替えは、すでに並べ替えられた順序で同じものを並べ替えるよりも約4倍高速です(オブジェクト配列とInt
配列の並べ替えは異なるアルゴリズムを使用しているので、バイアスを排除するために両方を並べ替えました)。
結果は次のとおりです。
_Shuffled Int array sort time: 1.3961209654808
Shuffled ColorObject array sort time: 3.14633798599243
NOnshuffled Int array sort time: 7.34714204072952
NOnshuffled ColorObject array sort time: 10.9310839772224
_
以下の参照用に私のコードがあります:
_class ElapsedTimer {
let startTime: CFAbsoluteTime
var endTime: CFAbsoluteTime?
init() {
startTime = CFAbsoluteTimeGetCurrent()
}
func stop() -> CFAbsoluteTime {
endTime = CFAbsoluteTimeGetCurrent()
return duration!
}
var duration: CFAbsoluteTime? {
if let endTime = endTime {
return endTime - startTime
} else {
return nil
}
}
}
public class CountedColor {
public private(set) var count: Int
public private(set) var color: UIColor
public init(color: UIColor, colorCount: Int) {
self.count = colorCount
self.color = color
}
}
var distributedIntArray = [Int]()
for value in 1..<1000000 {
distributedIntArray.append(value)
}
var distributedCountedColorArray = distributedIntArray.map{ CountedColor(color: UIColor.white, colorCount: $0) }
distributedCountedColorArray.shuffle()
distributedIntArray.shuffle()
var timer = ElapsedTimer()
distributedIntArray.sort()
print("Shuffled Int array sort time: \(timer.stop())")
timer = ElapsedTimer()
distributedCountedColorArray.sort{ return $0.count < $1.count }
print("Shuffled Color array sort time: \(timer.stop())")
timer = ElapsedTimer()
distributedIntArray.sort()
print("NOnshuffled Int array sort time: \(timer.stop())")
timer = ElapsedTimer()
distributedCountedColorArray.sort{ return $0.count < $1.count }
print("Non shuffled Color array sort time: \(timer.stop())")
_
私の配列shuffle()
メソッドは この投稿 から取得されました。私のElapsedTimer
は、単にCACurrentMediaTime()
関数をラップして使用します。
私の質問は、なぜこの動作が見られるのですか?特に私がオブジェクト配列をソートしているとき、それは確かに汎用ソートを使用しているはずです。 Swiftを使用しているのはどのような汎用ソートアルゴリズムですか?mergeSortのように最悪のケースと平均的なケースが同じであるということは確かにあり得ません。
Swiftは イントロソート を使用します。 ソースコード を見ると、選択したピボットが最初の要素であることがわかります。イントロソートのウィキペディアのページには次のように書かれています。
(...)、重要な操作の1つは、ピボットを選択することです。つまり、リストが分割される要素です。最も単純なピボット選択アルゴリズムは、リストの最初または最後の要素をピボットとして使用することです。これにより、ソートされた入力またはほぼソートされた入力の場合の動作が低下します。
したがって、実装の選択を考えると、Swiftのソートパフォーマンスがソートされた入力に対して最悪であることは完全に予測可能です。
OPの主張を簡単に再現したい人のために完全なベンチマークを作成しました: https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/extra/Swift/sort
参考までに、GNU ISO C++標準ライブラリは中央値3のピボットを使用します(stl_algo.h
ヘッダー)。
これは、次のように思われるこの質問の複製です: Swiftソートアルゴリズムの実装
さらに、シャッフルしたときにパフォーマンスが大幅に向上する理由は、シャッフルしたときにパフォーマンスがNlogNの上限に達していないためと考えられます。ソートされた配列をソートすると、おそらくその上限に近づくため、すべて同じソートになります。しかし、私はこれが単なる理論であることを知りません
Swift 5の進化では、IntroSortアルゴリズムは 'sort()'メソッドで修正バージョンのTimSort(2002年にTim Peters for Pythonによって最初に実装された)に置き換えられました: https ://github.com/Apple/Swift/blob/master/stdlib/public/core/Sort.Swift