[解決済み】ソートされた配列の処理が、ソートされていない配列より遅いのはなぜですか?
質問
500000個のランダムに生成されたリストがあります。
Tuple<long,long,string>
オブジェクトに対して、単純な "between" 検索を実行します。
var data = new List<Tuple<long,long,string>>(500000);
...
var cnt = data.Count(t => t.Item1 <= x && t.Item2 >= x);
ランダムな配列を生成し、ランダムに生成された100個の値に対して検索を実行すると
x
の場合、4秒程度で検索が完了します。を知っていること
ソートが検索に与える大きな効果
しかし、私はデータをソートすることにしました。
Item1
で、次に
Item2
であり、最後に
Item3
- を実行する前に、100回の検索を行いました。分岐予測により、ソートされたバージョンの方が少し速く実行されると予想していました。
Item1 == x
をチェックし、さらに
t.Item1 <= x
は、そのブランチを正しく "no take" と予測し、探索の末尾部分を高速化します。驚いたことに
ソートされた配列の場合、検索にかかる時間は2倍になります。
!
実験の順番を入れ替えたり、乱数生成器の種を変えてみたりしましたが、効果は同じでした。ソートされていない配列での検索は、ソートされている同じ配列での検索の約2倍の速さで走りました
この不思議な効果について、どなたか良い説明をお持ちの方はいらっしゃいませんか?私のテストのソースコードは以下の通りです。.NET 4.0を使用しています。
private const int TotalCount = 500000;
private const int TotalQueries = 100;
private static long NextLong(Random r) {
var data = new byte[8];
r.NextBytes(data);
return BitConverter.ToInt64(data, 0);
}
private class TupleComparer : IComparer<Tuple<long,long,string>> {
public int Compare(Tuple<long,long,string> x, Tuple<long,long,string> y) {
var res = x.Item1.CompareTo(y.Item1);
if (res != 0) return res;
res = x.Item2.CompareTo(y.Item2);
return (res != 0) ? res : String.CompareOrdinal(x.Item3, y.Item3);
}
}
static void Test(bool doSort) {
var data = new List<Tuple<long,long,string>>(TotalCount);
var random = new Random(1000000007);
var sw = new Stopwatch();
sw.Start();
for (var i = 0 ; i != TotalCount ; i++) {
var a = NextLong(random);
var b = NextLong(random);
if (a > b) {
var tmp = a;
a = b;
b = tmp;
}
var s = string.Format("{0}-{1}", a, b);
data.Add(Tuple.Create(a, b, s));
}
sw.Stop();
if (doSort) {
data.Sort(new TupleComparer());
}
Console.WriteLine("Populated in {0}", sw.Elapsed);
sw.Reset();
var total = 0L;
sw.Start();
for (var i = 0 ; i != TotalQueries ; i++) {
var x = NextLong(random);
var cnt = data.Count(t => t.Item1 <= x && t.Item2 >= x);
total += cnt;
}
sw.Stop();
Console.WriteLine("Found {0} matches in {1} ({2})", total, sw.Elapsed, doSort ? "Sorted" : "Unsorted");
}
static void Main() {
Test(false);
Test(true);
Test(false);
Test(true);
}
Populated in 00:00:01.3176257
Found 15614281 matches in 00:00:04.2463478 (Unsorted)
Populated in 00:00:01.3345087
Found 15614281 matches in 00:00:08.5393730 (Sorted)
Populated in 00:00:01.3665681
Found 15614281 matches in 00:00:04.1796578 (Unsorted)
Populated in 00:00:01.3326378
Found 15614281 matches in 00:00:08.6027886 (Sorted)
解決方法は?
ソートされていないリストを使用する場合、すべてのタプルにアクセスするのは メモリオーダー . これらはRAM上に連続的に割り当てられています。CPU はメモリに順次アクセスするのが好きです。なぜなら、次のキャッシュラインを推測で要求することができるので、必要なときに常に存在することになるからです。
リストをソートするときは、リストを ランダムオーダー なぜなら、ソートキーはランダムに生成されるからです。これは、タプルのメンバーへのメモリアクセスが予測不可能であることを意味します。CPUはメモリをプリフェッチできないので、タプルへのアクセスはほとんどすべてキャッシュミスになります。
の具体的な利点がよくわかる例です。 GCメモリ管理 一緒に割り当てられ、一緒に使用されるデータ構造は、非常にうまく機能します。これらは素晴らしい <強い 参照の局所性 .
キャッシュミスによるペナルティ 保存された分岐予測のペナルティを上回る この場合
に切り替えてみてください。
struct
-タプルです。これにより、タプルのメンバにアクセスするために実行時にポインタを参照する必要がなくなるため、パフォーマンスが回復します。
Chris Sinclairは、コメントで次のように指摘しています。 TotalCountが10,000以下の場合、ソートされたバージョンはより高速に処理されます。 となります。これは、小さなリスト CPUキャッシュに完全に収まる . メモリアクセスは予測できないかもしれませんが、ターゲットは常にキャッシュの中にあります。キャッシュからのロードにもサイクルがかかるので、若干のペナルティはあると思います。しかし、これは問題ではないようです。 CPUは複数の未解決のロードを処理できる その結果、スループットが向上しました。CPUはメモリ待ちの状態になると、できるだけ多くのメモリ操作をキューに入れるために、命令ストリームを先に進めます。この技術は、レイテンシーを隠すために使用されます。
このような動作は、最近のCPUの性能を予測することがいかに難しいかを物語っています。という事実があります。 わずか2倍遅くなる シーケンシャルメモリからランダムメモリにアクセスするとき、メモリレイテンシを隠すためにどれだけのことが隠されているのかがわかります。メモリアクセスは、50~200サイクルの間、CPUをストールさせることができます。この数字を考えると、ランダムメモリアクセスを導入した場合、プログラムは10倍遅くなることが予想されます。
関連
-
[解決済み】コンパイルエラー「未割り当てのローカル変数を使用しています」が発生したのはなぜですか?
-
[解決済み] 保護レベルによりアクセス不能になりました。
-
[解決済み] B "の印刷が "#"の印刷より劇的に遅いのはなぜですか?
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] なぜJavaでは2 * (i * i)の方が2 * i * iより速いのですか?
-
[解決済み] なぜ[]はlist()よりも速いのですか?
-
[解決済み】なぜFunc<T>ではなくExpression<Func<T>を使うのですか?
-
[解決済み】float < integerの比較で4倍遅くなるものがあるのはなぜですか?
-
[解決済み] 最新のx86-64 clangでソートされていない配列の処理とソートされた配列の処理が同じ速度になるのはなぜですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】ASP.NET Core Dependency Injectionのエラーです。アクティブ化しようとしているときに、タイプのサービスを解決できません。
-
[解決済み】"The ConnectionString property has not been initialized "を修正する方法
-
[解決済み】トランスポート接続からデータを読み取れない:既存の接続は、リモートホストによって強制的に閉じられました。
-
[解決済み】プロジェクトビルド時のエラー。エディタでスクリプトにコンパイルエラーがあるため、Playerのビルドにエラーが発生する
-
[解決済み】バックスラッシュを含むパス文字列のエスケープシーケンスが認識されない件
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み】C# - パスに不正な文字がある場合
-
[解決済み】ファイルへの読み書きの際に共有違反のIOExceptionが発生する C#
-
[解決済み] [Solved] .NETでスレッドの終了を待つには?
-
[解決済み】URLから画像をダウンロードする方法