Поток данных TPL и вызовы асинхронных методов

Я пытаюсь лучше понять всю концепцию параллельной обработки и настроил тестовые примеры. Поиграв с тестами, я вижу, что использование вызовов методов Async в Dataflow ActionBlock (или TransformBlock) не влияет положительно на производительность, а просто усложняет код. Правильно ли я предполагаю, что если я использую блоки потока данных, код внутри них не обязательно должен быть асинхронным, поток данных сам сделает его асинхронным. Или я упустил суть?


person Dimitri    schedule 06.09.2012    source источник


Ответы (2)


TPL Dataflow не поддерживает параллелизм или распараллеливание (хотя вы можете использовать такие вещи, как ActionBlock для распараллеливания его обработки), это то, что параллельный или параллельный код использует для передачи данных. Помимо прочего, это механизм передачи сообщений - своего рода альтернатива общим данным. Совместно используемые данные при использовании несколькими потоками требуют дорогостоящей синхронизации. Передача сообщений, если все сделано правильно, не требует синхронизации, потому что данные, с которыми нужно работать, инкапсулируются в сообщение, которое «отправляется» коду, который будет с ними работать.

TPL Dataflow - это то, что вы можете использовать, если у вас есть конкретный дизайн. Если вы специально не реализуете что-то вроде программирования на основе актеров или передача сообщений или неблокирующие сценарии производителя / потребителя, тогда поток данных TPL, скорее всего, усложнит ситуацию.

Если вы думаете, что можете разработать такую ​​систему, есть несколько полезных ресурсов по пониманию потока данных TPL (TDF), например видео Стивена Туба (члена параллельной группы Microsoft), а также Страница MSDN потока данных.

ОБНОВИТЬ:

Вы можете установить максимальную степень параллелизма для блока, но установка его выше, чем количество процессоров или ядер, часто приводит к обратным результатам. Предполагается, что каждое выполняемое действие более или менее связано с ЦП (использование ЦП на 100% во время выполнения). Если действия тратят много времени на ожидание (ожидание дескриптора ожидания, ожидание сообщений в насосе сообщений - что не является нормальным для действия, но для потока пользовательского интерфейса и т. Д.), То степень параллелизма выходит за рамки количество процессоров может иметь смысл (хотя это будет сложно настроить). Когда у вас действительно выполняется больше действий, чем процессоров, которые привязаны к процессору, вы действительно начинаете загружать ОС. ОС хочет отдать процессорное время каждому потоку (или каждому действию в данном случае), потому что он «работает». Когда не хватает процессоров для обхода, ОС начинает выполнять многозадачность с вытеснением, циклически отдавая процессорное время каждому активному потоку. Каждый раз, когда ОС отводит процессор от одного потока и передает его другому, это называется переключением контекста. . Это действительно дорого (в диапазоне 2000-8000 циклов процессора). Итак, ОС действительно тратит все свое время на переключение контекста, а не на выполнение ваших действий.

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

person Peter Ritchie    schedule 06.09.2012
comment
Да, я разрабатываю приложение производителя / потребителя с передачей сообщений. И я вижу, что Dataflow лучше всего подходит для этого (по сравнению с Tasks, ThreadPool и т. Д.). Но хотел знать, нужны ли мне асинхронные методы в ActionBlocks. - person Dimitri; 06.09.2012
comment
Вы можете установить уровень параллелизма с помощью ActionBlock. Я считаю, что по умолчанию не используется параллелизм (по одному блоку за раз, по порядку). См. msdn.microsoft.com/en-us/library/hh228609.aspx для некоторых деталей - person Peter Ritchie; 06.09.2012
comment
Боюсь, я недостаточно ясно задала свой вопрос. Я установил DoP как неограниченный (который, насколько я понимаю, не ограничивает количество параллельных потоков), но также сделал бы код, который выполняется внутри ActionBlock Async, ускоряет обработку, или это будет пустой тратой - person Dimitri; 06.09.2012
comment
@Dimitri Мой ответ был намного больше, чем мог бы быть комментарий, поэтому я обновил ответ. - person Peter Ritchie; 06.09.2012
comment
Спасибо, теперь в этом больше смысла. - person Dimitri; 06.09.2012
comment
Я не согласен с тем, что переключение контекста в данном случае является такой большой проблемой. Да, установка степени параллелизма больше, чем количество ядер, не сделает программу быстрее, если вычисления связаны с процессором. Но TPL Dataflow по умолчанию использует ThreadPool, что означает, что каждое действие в большинстве случаев не получает свой собственный поток. Вместо этого действия находятся в очереди, и ThreadPool пытается использовать оптимальное количество потоков для их выполнения. - person svick; 07.09.2012
comment
@svick Возможно, вы думаете о TPL и планировщике ThreadPool. По умолчанию ThreadPool составляет 25 потоков на процессор / ядро ​​- более чем достаточно, чтобы вызвать проблемы с переключением контекста. (ThreadPool.QueueUserWorkItem, не TPL ...) Но, если вы говорите TPL, что вам нужна максимальная степень параллелизма, вы говорите ему, что все должно иметь свой собственный поток (ну, вы просите, чтобы все выполнялось одновременно - в настоящее время единственный способ сделать это - дать чему-то отдельную тему). - person Peter Ritchie; 07.09.2012
comment
Фактически @svick это задокументировано здесь: msdn.microsoft.com/en- us / library / ff963552.aspx - person Peter Ritchie; 07.09.2012
comment
@PeterRitchie Из статьи, на которую вы ссылаетесь: «В большинстве случаев встроенные алгоритмы балансировки нагрузки в .NET Framework являются наиболее эффективным способом управления этими компромиссами». И TDF действительно использует TPL, который, в свою очередь, использует ThreadPool, поэтому я думаю о них. ThreadPool по умолчанию не имеет 25 потоков на ядро, он начинается с 1 потока на ядро, но это число может увеличиваться, если ThreadPool сочтет это подходящим (например, когда один из потоков блокируется). - person svick; 07.09.2012
comment
@PeterRitchie. Установка MaxDegreeOfParallelism на неограниченный не означает, что каждый элемент будет обрабатываться в собственном потоке, что действительно было бы ужасно неэффективным. Это означает, что он будет использовать столько потоков, сколько ThreadPool позволит ему, что в большинстве случаев будет около 1 на ядро. - person svick; 07.09.2012
comment
@svick они говорят, что встроенная балансировка нагрузки наиболее эффективна и что если вы измените MDOP, вы больше не будете использовать встроенную балансировку нагрузки. В противном случае произвольное увеличение степени параллелизма подвергнет вас риску переподписки процессора. Комментарий не имеет смысла. Это довольно легко показать, просто используя Parallel.ForEach с перегрузкой ParallelOptions. - person Peter Ritchie; 07.09.2012
comment
позвольте нам продолжить обсуждение в чате - person svick; 07.09.2012
comment
@svick Теперь ThreadPool регулируется начальным числом создаваемых потоков. Он создает новый поток только при необходимости раз в секунду. поэтому, если он начинается с 5 потоков, и вся ваша работа может быть завершена в течение секунды, он никогда не будет использовать другой поток, потому что у него есть время для повторного использования некоторых. Но если вы начнете действие, которое займет больше секунды или двух, вы увидите, что пул потоков создает больше потоков, если MDOP равен -1, это будет 25 * счетчик ЦП (но также потребуется 25 * счетчик ЦП секунд, чтобы попасть... - person Peter Ritchie; 07.09.2012

TPL Dataflow не подходит для всех видов параллельной обработки, а также не сделает ваш код волшебным образом быстрее.

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

Это может быть особенно полезно, если код внутри блока использует какой-то ресурс, которым нельзя делиться. Таким образом, вы можете максимально эффективно использовать этот общий ресурс, поскольку обработка этого блока не зависит от других блоков.

В общем, TDF наиболее подходит, если ваш код похож на конвейер: элемент поступает, обрабатывается на этапе 1, затем на этапе 2,… и, наконец, он поступает на вывод. Хотя сети с потоками данных могут быть намного сложнее. Но вы не должны пытаться форсировать это, если то, что вы хотите сделать, не подходит для TDF, вы правы в том, что вы просто усложняете свой код без всякой пользы.

person svick    schedule 06.09.2012
comment
Спасибо svick за вклад. Мой сценарий является прекрасным примером конвейера данных, в котором часть необработанных данных проходит различные стадии обработки и, наконец, сохраняется в базе данных. - person Dimitri; 07.09.2012