Parallel.ForEach vs Task.Factory.StartNew


267

Яка різниця між наведеними нижче фрагментами коду? Не будуть обидва використовувати нитки нитки?

Наприклад, якщо я хочу викликати функцію для кожного елемента колекції,

Parallel.ForEach<Item>(items, item => DoSomething(item));

vs

foreach(var item in items)
{
  Task.Factory.StartNew(() => DoSomething(item));
}

Відповіді:


302

Перший - набагато кращий варіант.

Parallel.ForEach, внутрішньо, використовує a Partitioner<T>для розподілу своєї колекції по робочих предметах. Він не буде виконувати одне завдання на предмет, а скоріше замість цього, щоб зменшити накладні витрати.

У другому варіанті буде заплановано одиницю Taskна предмет у колекції. Хоча результати будуть (майже) однаковими, це призведе до набагато більших витрат, ніж потрібно, особливо для великих колекцій, і призведе до того, що загальний час роботи буде повільнішим.

FYI - Партнером, що використовується, можна керувати, використовуючи відповідні перевантаження Parallel.ForEach , якщо це потрібно. Детальніше дивіться у розділі Спеціальні учасники на MSDN.

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

Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));

Роблячи це, ви все-таки скористаєтеся партнерами, але не блокуйте, поки операція не буде завершена.


8
IIRC, розділ за замовчуванням, виконаний Parallel.ForEach, також враховує кількість доступних апаратних потоків, що позбавляє вас від необхідності відпрацювати оптимальну кількість завдань для запуску. Перегляньте статтю « Шаблони паралельного програмування Microsoft» ; це чудово пояснює все це в ньому.
Мал Росс

2
@Mal: Сортування ... Це насправді не Партнер, а скоріше робота TaskScheduler. За замовчуванням TaskScheduler використовує новий ThreadPool, який зараз дуже добре справляється з цим.
Рід Копсей

Дякую. Я знав, що мав би залишитись у застереженні "Я не експерт, але ..." :)
Мал Росс

@ReedCopsey: Як приєднати завдання, розпочаті через Parallel.ForEach, до завдання обгортки? Так що, коли ви зателефонуєте .Wait () на обгортковому завданні він зависає, поки завдання, що працюють паралельно, не будуть виконані?
Костянтин Таркус

1
@Tarkus Якщо ви робите кілька запитів, вам краще просто використовувати HttpClient.GetString у кожному робочому елементі (у вашому паралельному циклі). Немає підстав ставити параметр асинхронізації всередині вже одночасного циклу, як правило ...
Рид Копсей

89

Я зробив невеликий експеримент із запуском методу "1 000 000 000 (один мільярд)" разів з "Паралельно.За" і один з "Завданням".

Я виміряв час процесора і виявив, що Паралель більш ефективний. Parallel.For розділяє ваше завдання на невеликі робочі елементи і виконує їх на всіх ядрах паралельно оптимально. Під час створення безлічі об'єктів завдань (FYI TPL буде використовувати внутрішнє об'єднання потоків) переміщуватиме кожне виконання кожної задачі, створюючи більше напруги в полі, що видно з експерименту нижче.

Я також створив невелике відео, в якому пояснюється базовий TPL, а також продемонстровано, як Parallel.For ефективніше використовує ваше ядро http://www.youtube.com/watch?v=No7QqSc5cl8 порівняно зі звичайними завданнями та потоками.

Експеримент 1

Parallel.For(0, 1000000000, x => Method1());

Експеримент 2

for (int i = 0; i < 1000000000; i++)
{
    Task o = new Task(Method1);
    o.Start();
}

Порівняння часу процесора


Це було б більш ефективно, і причина того, що створення ниток є дорогим. Експеримент 2 - дуже погана практика.
Тім

@ Georgi-це, будь ласка, потурбуйся, щоб більше говорити про те, що погано.
Шивпрасад Койрала

3
Вибачте, моя помилка, я мав би уточнити. Я маю на увазі створення завдань у циклі до 1000000000. Накладні витрати неможливо уявити. Не кажучи вже про те, що Паралель не може створювати більше 63 завдань одночасно, що робить її значно більш оптимізованою у випадку.
Georgi-it

Це справедливо для 1000000000 завдань. Однак, коли я обробляю зображення (повторно, збільшуючи фрактал) і роблю паралельний. Для ліній багато ядер простоюють, очікуючи, поки останні нитки закінчаться. Щоб зробити це швидше, я розділив дані на 64 робочі пакети та створив для них завдання. (Тоді Task.WaitAll чекати завершення.) Ідея полягає в тому, щоб простої теми підбирали робочий пакет, щоб допомогти закінчити роботу, замість того, щоб чекати 1-2 потоки, щоб закінчити їх (Parallel.For) призначений фрагмент.
Тедд Хансен

1
Що робить Mehthod1()у цьому прикладі?
Zapnologica

17

Parallel.ForEach буде оптимізувати (може навіть не запускати нові потоки) та блокувати, поки цикл не буде закінчений, і Task.Factory явно створить новий екземпляр завдання для кожного елемента та повернеться до їх закінчення (асинхронні завдання). Parallel.Foreach набагато ефективніший.


11

На мій погляд, найбільш реалістичний сценарій - це коли завдання мають завершити важку операцію. Підхід Шивпрасада більше зосереджений на створенні об'єктів / розподілі пам'яті, ніж на самих обчисленнях. Я зробив дослідження, назвавши наступний метод:

public static double SumRootN(int root)
{
    double result = 0;
    for (int i = 1; i < 10000000; i++)
        {
            result += Math.Exp(Math.Log(i) / root);
        }
        return result; 
}

Виконання цього методу займає близько 0,5 сек.

Я називав це 200 разів за допомогою паралелі:

Parallel.For(0, 200, (int i) =>
{
    SumRootN(10);
});

Тоді я називав це 200 разів старомодно:

List<Task> tasks = new List<Task>() ;
for (int i = 0; i < loopCounter; i++)
{
    Task t = new Task(() => SumRootN(10));
    t.Start();
    tasks.Add(t);
}

Task.WaitAll(tasks.ToArray()); 

Перша справа завершена в 26656 мс, друга в 24478 мс. Я повторював це багато разів. Кожен раз, коли другий підхід стає граничним швидше.


Використання Parallel.For - старомодний спосіб. Використання Завдання рекомендується для одиниць роботи, які не є рівномірними. Microsoft MVP та дизайнери TPL також згадують, що за допомогою завдань ефективніше використовувати нитки, тобто блокувати стільки, поки чекають завершення інших підрозділів.
Suncat2000
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.