ParallelEnumerable.GroupBy: насколько это лениво? - PullRequest
1 голос
/ 31 января 2012

MSDN говорит, что ParallelEnumerable.GroupBy параллельно группирует элементы последовательности в соответствии с заданной функцией выбора ключа.Поэтому мой вопрос: насколько это лениво?

Понятно, что ParallelQuery<IGrouping<,>> ленив.Но как насчет IGrouping<>, это тоже лениво?

Итак, если я сделаю следующее:

var entities = sites.AsParallel()
                         .Select(x => GetDataItemsFromWebsiteLazy(x))
                         .SelectMany(x => x)
                         .GroupBy(dataItem => dataItem.Url.Host)
                         .AsParallel()
                             .SelectMany(x => TransformToEntity(x));

будет ли TransformToEntity вызываться впервые после того, как все сайты будут получать результаты?Или как только первый метод GetDataItemsFromWebsiteLazy() даст результат, возвращающий элемент?

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

  1. запрос веб-сайта
  2. анализ ответа и извлечение URL другого сайта
  3. запрос сайта по извлеченному URL
  4. анализ ответа и создание сущности из полученных данных

Спасибо

1 Ответ

2 голосов
/ 31 января 2012

Расширение GroupBy на самом деле совсем не ленивое (или, точнее, вовсе не отсроченное ), что можно легко продемонстрировать с помощью следующей тестовой программы:

void Main()
{
    var source = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.AsParallel();
    var groupEven = GetEvenNumbersUsingGroupBy(source);
    // foreach (int num in groupEven) { }
}

IEnumerable<int> GetEvenNumbersUsingGroupBy(IEnumerable<int> source)
{
    Console.WriteLine("Method called: GetEvenNumbersUsingGroupBy");
    var grouped = source.GroupBy(i => i % 2);
    return grouped.Where(g => g.Key == 0).Single();
}

Эта программа выводит следующее:

Вызванный метод: GetEvenNumbersUsingGroupBy

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

Это отличается от обычного отложенного перечисления с использованием оператора yield, например:

void Main()
{
    var yieldEven = GetEvenNumbersUsingYield(source);
    foreach (int num in yieldEven) { }
    foreach (int num in yieldEven) { }
}

IEnumerable<int> GetEvenNumbersUsingYield(IEnumerable<int> source)
{
    Console.WriteLine("Method called: GetEvenNumbersUsingYield");
    foreach (int i in source)
        if ((i % 2) == 0)   
            yield return i;
}

Это печатает следующее:

Метод называется: GetEvenNumbersUsingYield
Метод называется: GetEvenNumbersUsingYield

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

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

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

...