В чем разница в производительности между использованием parallel.foreach и задачей внутри цикла foreach?

Я хотел бы знать, что является лучшим способом, или есть ли какие-либо документы/статьи, которые могут помочь мне определить, в чем разница между использованием Parallel.foreach и Task в обычном для каждого цикла, например:

случай 1 - Parallel.foreach:

Parallel.foreach
{
  // Do SOmething thread safe: parsing an xml and then save 
  // into a DB Server thry respoitory approach
}

случай 2 - Задача в foreach:

foreach
{
  Task t1 = Task.factory.startNew(()=>
  {
     //Do the same thing as case 1 that is thread safe
  }
}
Task.waitall()
  • Я провел свои собственные тесты, и результат показывает, что случай 1 работает намного лучше, чем случай 2. Соотношение примерно такое: последовательный против случая 1 против случая 2 = 5 с : 1 с : 4 с

Хотя есть почти 1:4 на корпусе 1 и на корпусе 2? Значит ли это, что мы всегда должны использовать parallel.foreach или parallel.for, если хотим работать параллельно внутри цикла?


person mting923    schedule 10.08.2013    source источник
comment
Парень, я не доверяю твоим результатам анализов... Эти цифры должны насторожить тебя.   -  person usr    schedule 10.08.2013
comment
@Will - создание задачи сильно отличается от создания темы. Смысл существования TPL.   -  person Henk Holterman    schedule 10.08.2013
comment
@ Уилл, спасибо за вклад, я думаю, у меня та же идея, но почему результат показывает разницу? И 4 раза...   -  person mting923    schedule 10.08.2013
comment
Я не понимаю, вы, кажется, говорите, что 4s для случая 1 лучше, чем 1s для случая 2.   -  person svick    schedule 10.08.2013
comment
извините за мой плохой английский + отсутствие опыта по размещению вопроса здесь. Я имею в виду, что в случае 1 для завершения работы для каждого цикла требовалось всего 1 секунда, в то время как для случая 2 для завершения каждого цикла требовалось около 4 секунд. [Отредактированный вопрос, чтобы исправить путаницу]   -  person mting923    schedule 12.08.2013
comment
Возможный дубликат Parallel.ForEach vs Task.Factory.StartNew   -  person Mohammad    schedule 16.02.2016


Ответы (3)


Во-первых, лучшая документация по этому вопросу — Часть V CLR через C#.

https://rads.stackoverflow.com/amzn/click/com/0735667454

Во-вторых, я ожидаю, что Parallel.Foreach будет работать лучше, потому что он будет не только создавать задачи, но и группировать их. В книге Джеффри Рихтера он объясняет, что задачи, которые запускаются индивидуально, будут помещены в очередь пула потоков. Блокировка фактической очереди пула потоков сопряжена с некоторыми накладными расходами. Для борьбы с этим сами Задачи имеют свою собственную очередь для Задач, которые они создают. Эта подочередь задач, хранящаяся в Tasks, может выполнять некоторую работу без блокировки!

Мне пришлось бы перечитать эту главу еще раз (глава 27), поэтому я не уверен, что Parallel.Foreach работает таким образом, но именно этого я и ожидал.

Блокировка, объясняет он, стоит дорого, потому что требует доступа к конструкции уровня ядра.

В любом случае не ожидайте, что они будут обрабатываться последовательно. Использование Parallel.Foreach с меньшей вероятностью будет обрабатываться последовательно, чем ключевое слово foreach из-за вышеупомянутых внутренних механизмов.

person Phillip Scott Givens    schedule 11.08.2013
comment
Я еще не начал читать книгу, но я согласен с вашей точкой зрения о функции «группы» из parallel.each. В моем parallel.each я ожидаю, что блок будет критическим разделом, но оказывается, что это не так, когда я реализую его в parallel.foreach. Я хотел бы прокомментировать больше после того, как я закончу читать главу. Спасибо за рекомендацию, эта книга выглядит многообещающе для укрепления моих знаний. - person mting923; 12.08.2013
comment
Я не думаю, что он говорит о механизме очереди задач непосредственно в контексте Parallel, но это либо в той главе, либо в следующей. - person Phillip Scott Givens; 13.08.2013

Что делает Parallel.ForEach(), так это то, что он создает небольшое количество Task для обработки итераций вашего цикла. Task относительно дешевы, но не бесплатны, поэтому это способствует повышению производительности. И тело вашего цикла выполняется быстро, улучшение может быть очень большим. Это наиболее вероятное объяснение поведения, которое вы наблюдаете.

person svick    schedule 11.08.2013
comment
Я не уверен, что понимаю последние два предложения ... о какой реализации вы говорите в отношении «тела вашего цикла»? параллельный.форич? или задача в цикле foreach? - person mting923; 12.08.2013
comment
@ mting923 mting923 Я имею в виду одну итерацию цикла, а не весь Task. - person svick; 12.08.2013

Сколько задач вы выполняете? Просто создание новой задачи может потребовать значительного количества времени, если вы достаточно зацикливаетесь. то есть следующее выполняется за 15 мс для первого блока и более 1 секунды для 2-го блока, а 2-й блок даже не запускает задачу. Раскомментируйте Start, и время увеличится почти до 3 секунд. WaitAll добавляет лишь небольшое количество.

static class Program
{
    static void Main()
    {
        const int max = 3000000;
        var range = Enumerable.Range(0, max).ToArray();
        {
            var sw = new Stopwatch();
            sw.Start();
            Parallel.ForEach(range, i => { });
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
        {
            var tasks = new Task[max];
            var sw = new Stopwatch();
            sw.Start();
            foreach (var i in range)
            {
                tasks[i] = new Task(()=> { });
                //tasks[i].Start();
            }
            //Task.WaitAll(tasks);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
    }
}
person Dax Fohl    schedule 10.08.2013
comment
Спасибо за пример кода, я попробую его, прежде чем вносить свой вклад. Но я также получил бы среднее время для каждого цикла для сравнения. - person mting923; 12.08.2013