Заказано PLINQ ForAll - PullRequest
       6

Заказано PLINQ ForAll

11 голосов
/ 18 марта 2011

Документация MSDN о сохранении порядка в PLINQ гласит следующее о ForAll().

  • Результат, когда исходная последовательность упорядочена : Выполняется недетерминированно параллельно
  • Результат, когда исходная последовательность неупорядочена : Выполняется недетерминистически параллельно

Означает ли эточто упорядоченное выполнение метода ForAll никогда не гарантируется?

Я раньше не использовал PLINQ, но следующий вопрос проверки кода показался мне подходящим для этого.Внизу моего ответа я пишу:

Events.AsParallel().AsOrdered().ForAll( eventItem =>
{
    ...
} );    

После прочтения документации я считаю, что AsOrdered() ничего не изменит?Я также подозреваю, что предыдущий запрос не может заменить простой цикл for, где важен порядок?Возможно, также будут происходить параллельные вызовы StringBuilder, что приведет к неправильному выводу?

Ответы [ 4 ]

15 голосов
/ 18 марта 2011

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

Поскольку ForAll ничего не возвращает, на самом деле это не имеет никакого эффекта, о котором я знаю.

Единственный способ сделать заказ относится к обработке будет завершать элемент 0 перед обработкой элемента 1, перед обработкой элемента 2 и т. д ... в этот момент у вас нет параллелизма.

7 голосов
/ 05 января 2014

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

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

public static void ForAllInApproximateOrder<TSource>(this ParallelQuery<TSource> source, Action<TSource> action) {

    Partitioner.Create( source )
               .AsParallel()
               .AsOrdered()
               .ForAll( e => action( e ) );

}

Это можно затем использовать следующим образом:

orderedElements.AsParallel()
               .ForAllInApproximateOrder( e => DoSomething( e ) );

Следует отметить, что в вышеприведенном методе расширения используется PLINQ ForAll, а не Parallel.ForEach, и поэтому он наследует потоковую модель, используемую совместно PLINQ (котораяотличается от того, что используется Parallel.ForEach - по моему опыту менее агрессивно по умолчанию).Аналогичный метод расширения с использованием Parallel.ForEach приведен ниже.

public static void ForEachInApproximateOrder<TSource>(this ParallelQuery<TSource> source, Action<TSource> action) {

    source = Partitioner.Create( source )
                        .AsParallel()
                        .AsOrdered();

    Parallel.ForEach( source , e => action( e ) );

}

Затем его можно использовать следующим образом:

orderedElements.AsParallel()
               .ForEachInApproximateOrder( e => DoSomething( e ) );

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

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

5 голосов
/ 18 марта 2011

AsOrdered() ничего бы не изменило - если вы хотите навести порядок в результате параллельного запроса, вы можете просто использовать foreach() ForAll(), чтобы воспользоваться преимуществом параллелизма , что означает выполнение побочного эффекта более чем для одного элемента в коллекции одновременно. Фактически, порядок применяется только к результатам запроса (порядок элементов в наборе результатов), но это не имеет ничего общего с ForAll(), поскольку ForAll() не влияет на порядок вообще.

В PLINQ цель состоит в том, чтобы максимизировать производительность при сохранении правильность. Запрос должен выполняться как как можно быстрее, но по-прежнему производят правильные результаты. В некоторых случаях, Корректность требует сохранения порядка исходной последовательности

Обратите внимание, что ForAll() не преобразует коллекцию (она не проецируется в новую коллекцию), она предназначена исключительно для выполнения побочных эффектов для результатов запроса PLINQ.

4 голосов
/ 18 марта 2011

Означает ли это, что упорядоченное выполнение метода ForAll никогда не гарантируется?

Да - порядок не гарантируется.

Распараллеливание означает, что работа распределяетсяв разные потоки, и их отдельные выходы затем объединяются.

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


Кроме того, если вы обращаетесь к таким объектам, как StringBuilder, из выполнения plinq, убедитесь, что эти объекты являются потокобезопасными, а также имейте в виду, что эта безопасность потоков может на самом деле замедлить plinq, чем непараллельный linq.

...