новый KeyValuePair <UInt32, UInt32> (i, j) .GetHashCode (); Высокая частота дубликатов

В поисках быстрого составного ключа для словаря я натолкнулся на аномалию, которую не могу ни понять, ни оправдать.

В ограниченном тестировании

Dictionary<KeyValuePair<UInt32, UInt32>, string>

значительно медленнее (200: 1), чем

Dictionary<KeyValuePair<UInt16, UInt16>, string>

Тест на две петли от 0 до 1000 Заполните, а затем ContainsKey

         Poplulate     ContainsKey  
UInt32    92085         86578  
UInt16     2201           431

Проблема в том, что

new KeyValuePair<UInt32, UInt32>(i, j).GetHashCode();

дает много дубликатов.
В цикле i и j 1024 создаются только 1024 уникальных хеш-значения.

На основе лавинного комментария от CasperOne были опробованы i * 31 и j * 97 (два простых числа), в результате чего 105280 уникален для 1024X1024. Еще много дубликатов. CasperOne Я знаю, что это не то же самое, что случайное. Но это не моя работа, чтобы рандомизировать ввод. GetHashCode () должен рандомизировать вывод.

Why the high number of duplicates?

Тот же цикл на

new KeyValuePair<UInt16, UInt16>(i, j).GetHashCode();

дает 1024 X 1024 уникальных хэш-кодов (идеально).

Int32 имеет ту же проблему.

Эти дублирующие хэш-значения убивают

Dictionary<KeyValuePair<UInt32, UInt32>, string> 

Tuple также генерирует много дубликатов, которые не ухудшаются в Int32 по сравнению с Int16.

Время создания необработанного KVP и необработанного KPV.GetHashCode аналогично.

Та же аномалия с HashSet.

Dictionary<KeyValuePair<UInt32, UInt32>, string> dKVPu32 = new Dictionary<KeyValuePair<UInt32, UInt32>, string>();
Dictionary<KeyValuePair<UInt16, UInt16>, string> dKVPu16 = new Dictionary<KeyValuePair<UInt16, UInt16>, string>();
KeyValuePair<UInt32, UInt32> kvpUint32;
KeyValuePair<UInt16, UInt16> kvpUint16;
int range = 1000;
Int32 hashCode;
HashSet<Int32> kvpUint32Hash = new HashSet<Int32>();
HashSet<Int32> kvpUint16Hash = new HashSet<Int32>();

Stopwatch sw = new Stopwatch();
sw.Start();
for (UInt32 i = 0; i < range; i++)
{
    for (UInt32 j = 0; j < range; j++)
    {
        kvpUint32 = new KeyValuePair<UInt32, UInt32>(i, j);
    }
}
Console.WriteLine("UInt32  raw " + sw.ElapsedMilliseconds.ToString());
//  7
sw.Restart();
for (UInt16 i = 0; i < range; i++)
{
    for (UInt16 j = 0; j < range; j++)
    {
        kvpUint16 = new KeyValuePair<UInt16, UInt16>(i, j);
    }
}
Console.WriteLine("UInt16  raw " + sw.ElapsedMilliseconds.ToString());
//  6
sw.Restart();
for (UInt32 i = 0; i < range; i++)
{
    for (UInt32 j = 0; j < range; j++)
    {
        hashCode = new KeyValuePair<UInt32, UInt32>(i, j).GetHashCode();
        kvpUint32Hash.Add(hashCode);
    }
}
Console.WriteLine("UInt32  GetHashCode " + sw.ElapsedMilliseconds.ToString() + "  unique count " + kvpUint32Hash.Count.ToString());
//  285   1024
sw.Restart();
for (UInt16 i = 0; i < range; i++)
{
    for (UInt16 j = 0; j < range; j++)
    {
        hashCode = new KeyValuePair<UInt16, UInt16>(i, j).GetHashCode();
        kvpUint16Hash.Add(hashCode);
    }
}
Console.WriteLine("UInt16  GetHashCode " + sw.ElapsedMilliseconds.ToString() + "  unique count " + kvpUint16Hash.Count.ToString());
//  398 1000000
sw.Restart();
Console.ReadLine();
for (UInt32 i = 0; i < range; i++)
{
    for (UInt32 j = 0; j < range; j++)
    {
        dKVPu32.Add(new KeyValuePair<UInt32, UInt32>(i, j), String.Format("{0} {1}", i.ToString(), j.ToString()));
    }
}
Console.WriteLine("hsKVPu32 pop " + sw.ElapsedMilliseconds.ToString());
//  92085
sw.Restart();
for (UInt32 i = 0; i < range; i++)
{
    for (UInt32 j = 0; j < range; j++)
    {
        if (!dKVPu32.ContainsKey(new KeyValuePair<UInt32, UInt32>(i, j))) Debug.WriteLine("Opps"); ;
    }
}
Console.WriteLine("hsKVPu32 find " + sw.ElapsedMilliseconds.ToString());
//  86578
dKVPu32.Clear();
dKVPu32 = null;
GC.Collect();
sw.Restart();
for (UInt16 i = 0; i < range; i++)
{
    for (UInt16 j = 0; j < range; j++)
    {
        dKVPu16.Add(new KeyValuePair<UInt16, UInt16>(i, j), String.Format("{0} {1}", i.ToString(), j.ToString()));
    }
}
Console.WriteLine("hsKVPu16 pop " + sw.ElapsedMilliseconds.ToString());
//   2201
sw.Restart();
for (UInt16 i = 0; i < range; i++)
{
    for (UInt16 j = 0; j < range; j++)
    {
        if (!dKVPu16.ContainsKey(new KeyValuePair<UInt16, UInt16>(i, j))) Debug.WriteLine("Opps"); ;
    }
}
sw.Stop();
Console.WriteLine("hsKVPu16 find " + sw.ElapsedMilliseconds.ToString());
//  431

Постскриптум Самый быстрый - это упаковать .E.G. ((UInt32) int1 & lt; & lt; 16) | int2;

Хеш первого столбца UInt32 равен хешу KVP следующих двух.

2281371105 8 992
2281371104 8 993
2281371107 8 994

2281371145 0 0
2281371147 0 2
2281371149 0 4
2281371151 0 6
2281371137 0 8

2281371144 0 1
2281371146 0 3
2281371148 0 5
2281371150 0 7
2281371136 0 9

2281371144 1 0
2281371145 1 1
2281371146 1 2
2281371147 1 3
2281371148 1 4
2281371149 1 5
2281371150 1 6
2281371151 1 7
2281371136 1 8
2281371137 1 9

2281371147 2 0
2281371146 2 1
2281371144 2 3
2281371151 2 4
2281371150 2 5
2281371149 2 6
2281371148 2 7
2281371139 2 8

Единственный образец, который я нашел, - это то, что либо сумма, либо разница, либо KVP совпадают.
Но не смог найти образец, когда суммировать, а когда вычитать.
Это плохой хэш, поэтому знание того, что это такое, мало что значит.

Ответы на вопрос(4)

Ваш ответ на вопрос