Подтвердить что ты не робот

Оптимизация поисковых запросов: поиск ключевых слов по ключевым словам и поиск по индексу массива

Я пишу 7-карточный покерный оценщик как один из моих любимых проектов. Пытаясь оптимизировать свою скорость (мне нравится вызов), я был потрясен, обнаружив, что производительность поиска словаря была довольно медленной по сравнению с поиском индекса массива.

Например, я запустил этот примерный код, который перечисляет все 52 выбирает 7 = 133 784 560 возможных 7 карт:

var intDict = new Dictionary<int, int>();
var intList = new List<int>();
for (int i = 0; i < 100000; i ++)
{
    intDict.Add(i, i);  
    intList.Add(i);
}

int result;

var sw = new Stopwatch();
sw.Start();
for (int card1 = 0; card1 < 46; card1++)
  for (int card2 = card1 + 1; card2 < 47; card2++)
    for (int card3 = card2 + 1; card3 < 48; card3++)
      for (int card4 = card3 + 1; card4 < 49; card4++)
        for (int card5 = card4 + 1; card5 < 50; card5++)
          for (int card6 = card5 + 1; card6 < 51; card6++)
            for (int card7 = card6 + 1; card7 < 52; card7++)
              result = intDict[32131]; // perform C(52,7) dictionary key lookups
sw.Stop();
Console.WriteLine("time for dictionary lookups: {0} ms", sw.ElapsedMilliseconds);

sw.Reset();

sw.Start();
for (int card1 = 0; card1 < 46; card1++)
  for (int card2 = card1 + 1; card2 < 47; card2++)
    for (int card3 = card2 + 1; card3 < 48; card3++)
      for (int card4 = card3 + 1; card4 < 49; card4++)
        for (int card5 = card4 + 1; card5 < 50; card5++)
          for (int card6 = card5 + 1; card6 < 51; card6++)
            for (int card7 = card6 + 1; card7 < 52; card7++)
              result = intList[32131]; // perform C(52,7) array index lookups
sw.Stop();
Console.WriteLine("time for array index lookups: {0} ms", sw.ElapsedMilliseconds);

который выводит:

time for dictionary lookups: 2532 ms
time for array index lookups: 313 ms

Ожидается ли такое поведение (снижение производительности в 8 раз)? IIRC, словарь имеет, в среднем, O (1) поиск, в то время как массив имеет наихудший поиск O (1), поэтому я ожидаю, что поиск массива будет быстрее, но не этим!

В настоящее время я храню рейтинг в покере в Словаре. Я полагаю, что если это так быстро, как словарный поиск может быть, я должен пересмотреть мой подход и использовать массивы вместо этого, хотя индексация ранжирования будет немного сложнее, и мне, вероятно, придется задать еще один вопрос об этом.

4b9b3361

Ответ 1

Не забывайте, что в заметках Big-O указывается, как растет сложность по размеру (и т.д.) - это не дает никаких указаний о постоянных факторах. Поэтому иногда даже линейный поиск ключей быстрее, чем поиск в словаре, когда имеется достаточно мало ключей. В этом случае вы даже не выполняете поиск с массивом, а просто выполняете операцию индексирования.

Для прямого поиска индексов массивы в основном идеальны - это всего лишь случай

pointer_into_array = base_pointer + offset * size

(И затем разыменовать указатель.)

Выполнение поиска в словаре относительно сложно - очень быстро по сравнению с (скажем) линейным поиском по ключу, когда есть много ключей, но гораздо сложнее, чем прямой поиск в массиве. Он должен вычислить хэш ключа, а затем определить, в каком ведре он должен находиться, возможно, иметь дело с повторяющимися хэшами (или дублирующими ведрами), а затем проверить равенство.

Как всегда, выберите правильную структуру данных для задания - и если вам действительно удастся просто индексировать в массив (или List<T>), тогда да, это будет ослепительно быстро.

Ответ 2

Ожидается ли этот тип поведения (снижение производительности в 8 раз)?

Почему бы и нет? Каждый поиск массива почти интенсивный/небрежный, в то время как поиск по словарю может потребоваться, по крайней мере, для дополнительного вызова подпрограммы.

Точка их обоих - O (1) означает, что даже если у вас есть 50 раз больше предметов в каждой коллекции, снижение производительности по-прежнему остается лишь фактором того, что есть (8).

Ответ 3

Что-то может занять тысячелетие и все еще быть O (1).

Если вы пропустите этот код в окне разборки, вы быстро поймете, в чем разница.

Ответ 4

Поиск массива - это самая быстрая вещь, которую вы можете сделать - по сути, это всего лишь один бит арифметики указателя, чтобы перейти от начала массива к элементу, который вы хотели найти. С другой стороны, поиск словаря, вероятно, будет несколько более медленным, так как ему нужно делать хэширование и заботиться о поиске правильного ведра. Хотя ожидаемое время выполнения также O (1) - алгоритмические константы больше, поэтому он будет медленнее.

Ответ 5

Добро пожаловать в нотацию Big-O. Вы всегда должны учитывать, что существует постоянный фактор.

Выполнение одного Dict-Lookup, конечно, намного дороже, чем поиск в массиве.

Big-O сообщает вам, как масштабируются алгоритмы. Удвойте количество поисков и посмотрите, как изменяются цифры: оба должны удвоиться.

Ответ 6

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

В примечании к реализации; в .NET словари - это по существу хешируемые. Вы можете несколько улучшить свою эффективность поиска ключей, гарантируя, что ваши хеши ключей будут иметь большое пространство уникальных значений. Похоже, что в вашем случае вы используете простое целое в качестве ключа (который, как я считаю, хешируют по своему собственному значению) - так что это может быть лучше, чем вы можете.

Ответ 7

Стоимость извлечения элемента из Dictionary - это O (1), но это потому, что словарь реализован как хэш-таблица - так вы должны сначала вычислить значение хэша, чтобы узнать, какой элемент вернуть. Hashtables часто не так эффективны - но они хороши для больших наборов данных или наборов данных, которые имеют множество уникальных значений хеша.

Список (кроме слова мусора, используемого для очернения массива, а не связанного списка!) будет быстрее, так как он вернет значение, непосредственно вычисляя возвращаемый элемент.