非常に小さなコレクションの場合、違いはごくわずかです。範囲の下限 (500k アイテム) では、多くのルックアップを行っている場合に違いが見え始めます。バイナリ検索は O(log n) になりますが、ハッシュ ルックアップは O(1), amortizedになります。これは真の定数と同じではありませんが、バイナリ検索よりもパフォーマンスを低下させるには、かなりひどいハッシュ関数が必要です。
(「ひどいハッシュ」と言うとき、私は次のようなことを意味します:
hashCode()
{
return 0;
}
ええ、それ自体は非常に高速ですが、ハッシュマップがリンクされたリストになります.)
ialiashkevichは、配列と Dictionary を使用して 2 つのメソッドを比較する C# コードを書きましたが、キーに Long 値を使用していました。ルックアップ中に実際にハッシュ関数を実行するものをテストしたかったので、そのコードを変更しました。String 値を使用するように変更し、populate セクションと lookup セクションを独自のメソッドにリファクタリングして、プロファイラーで見やすくしました。比較のポイントとして、Long 値を使用するコードも残しました。最後に、カスタムの二分探索関数を取り除き、Array
クラス内のものを使用しました。
そのコードは次のとおりです。
class Program
{
private const long capacity = 10_000_000;
private static void Main(string[] args)
{
testLongValues();
Console.WriteLine();
testStringValues();
Console.ReadLine();
}
private static void testStringValues()
{
Dictionary<String, String> dict = new Dictionary<String, String>();
String[] arr = new String[capacity];
Stopwatch stopwatch = new Stopwatch();
Console.WriteLine("" + capacity + " String values...");
stopwatch.Start();
populateStringArray(arr);
stopwatch.Stop();
Console.WriteLine("Populate String Array: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
populateStringDictionary(dict, arr);
stopwatch.Stop();
Console.WriteLine("Populate String Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
Array.Sort(arr);
stopwatch.Stop();
Console.WriteLine("Sort String Array: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
searchStringDictionary(dict, arr);
stopwatch.Stop();
Console.WriteLine("Search String Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
searchStringArray(arr);
stopwatch.Stop();
Console.WriteLine("Search String Array: " + stopwatch.ElapsedMilliseconds);
}
/* Populate an array with random values. */
private static void populateStringArray(String[] arr)
{
for (long i = 0; i < capacity; i++)
{
arr[i] = generateRandomString(20) + i; // concatenate i to guarantee uniqueness
}
}
/* Populate a dictionary with values from an array. */
private static void populateStringDictionary(Dictionary<String, String> dict, String[] arr)
{
for (long i = 0; i < capacity; i++)
{
dict.Add(arr[i], arr[i]);
}
}
/* Search a Dictionary for each value in an array. */
private static void searchStringDictionary(Dictionary<String, String> dict, String[] arr)
{
for (long i = 0; i < capacity; i++)
{
String value = dict[arr[i]];
}
}
/* Do a binary search for each value in an array. */
private static void searchStringArray(String[] arr)
{
for (long i = 0; i < capacity; i++)
{
int index = Array.BinarySearch(arr, arr[i]);
}
}
private static void testLongValues()
{
Dictionary<long, long> dict = new Dictionary<long, long>(Int16.MaxValue);
long[] arr = new long[capacity];
Stopwatch stopwatch = new Stopwatch();
Console.WriteLine("" + capacity + " Long values...");
stopwatch.Start();
populateLongDictionary(dict);
stopwatch.Stop();
Console.WriteLine("Populate Long Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
populateLongArray(arr);
stopwatch.Stop();
Console.WriteLine("Populate Long Array: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
searchLongDictionary(dict);
stopwatch.Stop();
Console.WriteLine("Search Long Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
searchLongArray(arr);
stopwatch.Stop();
Console.WriteLine("Search Long Array: " + stopwatch.ElapsedMilliseconds);
}
/* Populate an array with long values. */
private static void populateLongArray(long[] arr)
{
for (long i = 0; i < capacity; i++)
{
arr[i] = i;
}
}
/* Populate a dictionary with long key/value pairs. */
private static void populateLongDictionary(Dictionary<long, long> dict)
{
for (long i = 0; i < capacity; i++)
{
dict.Add(i, i);
}
}
/* Search a Dictionary for each value in a range. */
private static void searchLongDictionary(Dictionary<long, long> dict)
{
for (long i = 0; i < capacity; i++)
{
long value = dict[i];
}
}
/* Do a binary search for each value in an array. */
private static void searchLongArray(long[] arr)
{
for (long i = 0; i < capacity; i++)
{
int index = Array.BinarySearch(arr, arr[i]);
}
}
/**
* Generate a random string of a given length.
* Implementation from https://stackoverflow.com/a/1344258/1288
*/
private static String generateRandomString(int length)
{
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[length];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
return new String(stringChars);
}
}
いくつかの異なるサイズのコレクションの結果を次に示します。(時間はミリ秒単位です。)
500000 Long 値...
Long ディクショナリの入力: 26
Long 配列の入力: 2
Long ディクショナリの検索: 9
Long 配列の検索: 80
500000 文字列値...
文字列配列の入力: 1237
文字列辞書の入力: 46
文字列配列の並べ替え: 1755
文字列辞書の検索: 27
文字列配列の検索: 1569
1000000 長い値...
長い辞書の入力: 58
長い配列の入力: 5
長い辞書の検索: 23
長い配列の検索: 136
1000000 文字列値...
入力文字列配列: 2070
入力文字列ディクショナリ: 121
ソート文字列配列: 3579
検索文字列ディクショナリ: 58
検索文字列配列: 3267
3000000 長い値...
長い辞書の入力: 207
長い配列の入力: 14
長い辞書の検索: 75
長い配列の検索: 435
3000000 文字列値...文字列配列の入力
: 5553文字列辞書の入力
: 449
文字列配列の並べ替え: 11695
文字列辞書の検索: 194
文字列配列の検索: 10594
10000000 長い値...
長い辞書の入力: 521
長い配列の入力: 47
長い辞書の検索: 202
長い配列の検索: 1181
10000000 文字列値...入力
文字列配列: 18119入力
文字列ディクショナリ: 1088
ソート文字列配列: 28174
検索文字列ディクショナリ: 747
検索文字列配列: 26503
比較のために、最後にプログラムを実行したときのプロファイラーの出力 (1,000 万件のレコードとルックアップ) を示します。関連する機能を強調しました。上記のストップウォッチのタイミング指標とほぼ一致しています。
ディクショナリ ルックアップは二分探索よりもはるかに高速であり、(予想どおり) その違いはコレクションが大きいほど顕著であることがわかります。したがって、妥当なハッシュ関数 (競合がほとんどなくかなり高速) を使用している場合、ハッシュ ルックアップは、この範囲内のコレクションのバイナリ検索よりも優れているはずです。