1/7/2010 2:08:05 PM

Намедни тестируя приложение на большом количестве данных наткнулся на кусок кода, смысл которого был прост и привычен: выбрать все элементы коллекции А, идентификаторы которых присутствуют в коллекции Б.

Код выглядел примерно так:

//items is a List<MyItem>
//itemIds is a List<long>

var resultItems = items.Where(x => itemIds.Contains(x.Id));

Можно было делать join, но он в итоге всё равно в нечно подобное выражается, поэтому разницы нет.
Время выполнения этой штуки – 6.7 секунды. Да, там полно элементов, что-то десятки тысяч, но всё же – почти 7 секунд – это неприемлемо долго.

Оптимизировалось всё это дело введением одной строчки кода:

var hashedIds = new HashSet<long>(itemIds); //и дальше вместо itemIds используем hashedIds

В общем-то оно и понятно, что хэш в подобных вещах быстрее.

Время выполнения вот этой строчки (создание хэш-таблицы) – порядка 100 милисекунд.
Время выполнения выборки – порядка 40 милисекунд.

Итого – 150 милисекунд вместо 6.7 секунд. Неплохо :)

P.S. Поковыряйтесь в профайлером – много интересного найдёте. Я нашёл :)

Comments

1/8/2010 3:46:55 AM

Vadim

Вооот, а можно было бы список на части разбить и многопоточный поиск запустить Smile

Vadim

1/8/2010 10:21:58 AM

Alexey Raga

Ага, ага. Или на C++ переписать, как некоторые советовали тут ;)

Про многопоточный - одно другому не мешает, на самом деле. Эта вещь и работает в многопоточной среде. Таких "поисков" параллельно много выполняется.

Кстати, если посмотреть на execution plan SQL Server'а на некоторых запросах - он именно это и делает.

Alexey Raga Australia

1/8/2010 10:56:45 AM

Vadim

Ну собственно это самый логичный способ, если нельзя алгоритм поменять, приходится сокращать количество данных. Алгоритм SQL Server поменять не может, он все должен делать честно, а разбить данные может. Некоторые идут дальще и делают sharding (непереводимо Smile

Но правильный путь - поменять алгоритм или структуру данных (что на самом деле одно и тоже практически). И только потом уже всякие сомнительные методы применять.

Так что ты все правильно сделал Smile

Vadim

1/14/2010 4:45:46 PM

Alexey Zuev

На самом деле использовать Join можно было. Результат его выполнения - это не просто проверка методом Contains() в обычном листе. Он сделал бы то же самое, что и ты делаешь вручную. Единственное отличие - он создаст Dictionary вместо HashSet. Поэтому конкретно в этом случае твой вариант будет немного быстрее.

Alexey Zuev Russia

1/15/2010 5:01:09 AM

Alexey Raga

Немного - это секунд на шесть с половиной.
Потому, что я пробовал.

Alexey Raga Australia

1/15/2010 11:21:39 AM

Alexey Zuev

я тоже решил попробовать.
Если взять такой пример:

List<long> items = new List<long>();
List<long> itemIds = new List<long>();
FillItems(items);
FillItems(itemIds);

Stopwatch sw = new Stopwatch();
long count;

sw.Start();
var hashedIds = new HashSet<long>(itemIds);
var hashResultItems = items.Where(x => hashedIds.Contains(x));
count = hashResultItems.Count();
sw.Stop();
Console.WriteLine("Hash: {0} ms. Count: {1}\n", sw.ElapsedMilliseconds, count);

sw.Reset();
sw.Start();
var joinResultItems = from item in items
          join itemId in itemIds.Distinct() on item equals itemId
          select item;
count = joinResultItems.Count();
sw.Stop();
Console.WriteLine("Join: {0} ms. Count: {1}\n", sw.ElapsedMilliseconds, count);

sw.Reset();
sw.Start();
var resultItems = items.Where(x => itemIds.Contains(x));
count = resultItems.Count();
sw.Stop();
Console.WriteLine("Normal: {0} ms. Count: {1}\n", sw.ElapsedMilliseconds, count);

результаты получились такие:

Hash: 22 ms. Count: 19742
Join: 47 ms. Count: 19742
Normal: 16144 ms. Count: 19742

метод FillItems был таким:
private static void FillItems(List<long> items)
{
  for (int i = 0; i < 50000; i++)
  {
    items.Add(rnd.Next(100000));
  }
}

если items будет коллекцией из reference классов, то измениться здесь вроде бы не должно ничего. так как все равно по чему foreach делать

Alexey Zuev Russia

1/17/2010 10:31:16 AM

Alexey Raga

Хм, действительно.
Быть может, если вместо простого List<long> есть лист типов MyObject { Id:long, ...} картинка меняется (например потому, что уже не воспользуешься .Sort() чтобы всё по-быстрому сделать).
Я не знаю.
Но join - это первое, что я попробовал. В моём случае выигрыша не было практически.

Alexey Raga Australia

Comments are closed

Powered by BlogEngine.NET 1.6.0.0

About the author

Alexey Raga Alexey Raga
.NET software developer.

E-mail me Send mail

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

Sign in