plinq для больших списков занимает огромное время

#c# #c#-4.0 #plinq

#c# #c #-4.0 #plinq

Вопрос:

У меня есть два списка воспроизведения в памяти и потребители, в одном из которых 15 миллионов объектов, а в другом около 3 миллионов.

ниже приведены несколько запросов, которые я запускаю..

 consumersn=consumers.AsParallel()
                    .Where(w => plays.Any(x => x.consumerid == w.consumerid))
                    .ToList();


List<string> consumerids = plays.AsParallel()
                                .Where(w => w.playyear == group_period.year 
                                         amp;amp; w.playmonth == group_period.month 
                                         amp;amp; w.sixteentile == group_period.group)
                                .Select(c => c.consumerid)
                                .ToList();


int groupcount = plays.AsParallel()
                      .Where(w => w.playyear == period.playyear 
                               amp;amp; w.playmonth == period.playmonth 
                               amp;amp; w.sixteentile == group 
                               amp;amp; consumerids.Any(x => x == w.consumerid))
                      .Count();
  

Несмотря на это, я использую 16-ядерный компьютер с 32 ГБ оперативной памяти. выполнение первого запроса заняло около 20 часов..

Я делаю что-то не так..

Вся помощь искренне приветствуется.

Спасибо

Комментарии:

1. Профилировщик здесь ваш друг. Но похоже, что вы выполняете 15 миллионов * 3 миллиона операций в своем самом первом запросе здесь.

Ответ №1:

Первый запрос LINQ очень неэффективен, распараллеливание может вам только помочь.

Объяснение: Когда вы пишете consumers.Where(w => plays.Any(x => x.consumerid == w.consumerid)) , это означает, что для каждого объекта в consumer вы потенциально будете перебирать весь plays список, чтобы найти затронутых потребителей. Таким образом, это максимум 3 миллиона пользователей, умноженных на 15 миллионов игр = 45 триллионов операций. Даже для 16 ядер это составляет около 2,8 триллиона операций на ядро.

Итак, первым шагом здесь было бы сгруппировать все воспроизведения по их идентификаторам потребителей и кэшировать результат в соответствующей структуре данных:

 var playsByConsumerIds = plays.ToLookup(x => x.consumerid, StringComparer.Ordinal);
  

Затем ваш первый запрос становится:

 consumersn = consumers.Where(w => playsByConsumerIds.Contains(w.consumerid)).ToList();
  

Этот запрос должен быть намного быстрее, даже без какого-либо распараллеливания.

Я не могу исправить следующие запросы, потому что я не совсем понимаю, что именно вы делаете group_period , но я бы предложил использовать GroupBy или ToLookup для создания всех групп за один проход.

Комментарии:

1. Спасибо.. Я буду читать в ToLookup. Что касается двух других запросов.. они происходят внутри цикла foreach, и там не используется предложение groupby.. Единственная причина получить идентификаторы пользователей во втором запросе — иметь возможность использовать его в третьем запросе. Так будет ли правильно меняться. Выберите (c => c.consumerid) во втором запросе к . ToLookup(x => x.consumerid, StringComparer. Порядковый номер); и идентификаторы пользователей. Любой (x => x == w.consumerid) в consumerids . Содержит (w.consumerid)

2. Я предлагаю, чтобы вы, вероятно, могли использовать a GroupBy , чтобы избежать цикла для второго и третьего запроса.

3. Извините, не могу понять, что вы имеете в виду, используя ‘GroupBy’, чтобы избежать цикла .. не могли бы вы привести пример.. Предложения where во втором и третьем запросе используют значения, поступающие из разных источников — group_period и (period и group) соответственно..

Ответ №2:

Выполнение первого запроса заняло 20 часов, потому plays.Any(x => x.consumerid == w.consumerid) что нужно пройти весь список из 15 000 000 воспроизведений каждый раз consumerid , когда его там нет.

Вы можете ускорить это, создав хэш-набор всех идентификаторов потребителей plays , например:

 var consumerIdsInPlays = new HashSet<string>(plays.Select(p => p.consumerid));
  

Теперь ваш первый запрос можно переписать для поиска O (1):

 consumersn=consumers
    .AsParallel()
    .Where(w => consumerIdsInPlays.Contains(w.consumerid))
    .ToList();
  

Комментарии:

1. могу ли я использовать Hashset для второго запроса и будет ли это быстрее?? Также я должен изменить идентификаторы пользователей. Любой (x => x == w.consumerid) в consumerids . Содержит (w.consumerid) в третьем запросе. это будет быстрее ?? Tx

2. @Arnab Второй запрос не выполняет поиск по спискам, поэтому он не будет быстрее. Третий запрос можно выполнить быстрее, если обратиться consumerids к хэш-набору и использовать Contains вместо Any . Вы также можете заменить Where(cond).Count() на Count(cond) .