Równolegle.ForEach vs Task.Fabryka.StartNew

Jaka jest różnica między poniższymi fragmentami kodu? Czy oba nie będą używać wątków threadpool?

Na przykład, jeśli chcę wywołać funkcję dla każdego elementu w zbiorze,

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

vs

foreach(var item in items)
{
  Task.Factory.StartNew(() => DoSomething(item));
}
Author: stackoverflowuser, 2011-02-15

4 answers

Pierwszy jest o wiele lepszym rozwiązaniem.

Równolegle.ForEach, wewnętrznie, używa Partitioner<T> aby rozłożyć swoją kolekcję na przedmioty robocze. Nie zrobi jednego zadania na przedmiot, ale raczej wsadowo, aby obniżyć koszty ogólne.

Druga opcja zaplanuje pojedynczy Task na element w Twojej kolekcji. Podczas gdy wyniki będą (prawie) takie same, wprowadzi to znacznie więcej kosztów ogólnych niż jest to konieczne, szczególnie w przypadku dużych kolekcji i spowoduje, że ogólne czasy uruchamiania wolniej.

FYI-zastosowany Partycjoner może być kontrolowany za pomocą odpowiedniego przeciążenia do równoległego.ForEach , jeśli sobie tego życzysz. Aby uzyskać więcej informacji, zobacz własne Partycjonery {[7] } w MSDN.

Główną różnicą w czasie wykonywania jest to, że druga będzie działać asynchronicznie. Można to powielać za pomocą funkcji Parallel.ForEach robiąc:

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

Robiąc to, nadal korzystasz z partycji, ale nie blokuj dopóki operacja nie zostanie zakończona.

 270
Author: Reed Copsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-15 20:37:25

Przeprowadziłem mały eksperyment polegający na uruchomieniu metody "1000000000" razy z "Parallel".Dla "i jeden z obiektami" zadanie".

Zmierzyłem czas procesora i stwierdziłem, że Parallel jest bardziej wydajny. Równolegle.For dzieli twoje zadanie na małe elementy robocze i wykonuje je na wszystkich rdzeniach równolegle w optymalny sposób. Podczas tworzenia wielu obiektów zadań (FYI TPL będzie używać puli wątków wewnętrznie) przeniesie każde wykonanie na każdym zadaniu tworząc więcej stresu w polu, co jest widoczne z eksperyment poniżej.

Stworzyłem również mały filmik, który wyjaśnia podstawowe TPL, a także zademonstrował, jak równoległe.Dla wydajniejszego wykorzystania Rdzenia http://www.youtube.com/watch?v=No7QqSc5cl8 w porównaniu do zwykłych zadań i wątków.

Eksperyment 1

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

Eksperyment 2

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

Porównanie czasu procesora

 73
Author: Shivprasad Koirala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-29 03:45:47

Równolegle.ForEach optymalizuje (może nawet nie uruchamiać nowych wątków) i blokuje do czasu zakończenia pętli i zadania.Factory jawnie utworzy nową instancję zadania dla każdego elementu i powróci przed ich zakończeniem (zadania asynchroniczne). Równolegle.Foreach jest znacznie bardziej wydajny.

 16
Author: Sogger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-15 20:39:19

Moim zdaniem najbardziej realistyczny scenariusz jest wtedy, gdy zadania mają ciężką operację do wykonania. Podejście shivprasada koncentruje się bardziej na tworzeniu obiektów/alokacji pamięci niż na samym przetwarzaniu danych. Zrobiłem badania wywołując następującą metodę:

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; 
}

Wykonanie tej metody zajmuje około 0.5 sek.

Nazwałem go 200 razy używając Parallel:

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

Potem nazwałem to 200 razy staromodnym sposobem:

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()); 

Pierwsza sprawa zakończona w 26656ms, druga w 24478ms. powtarzałem to wiele razy. Za każdym razem drugie podejście jest marginalnie szybsze.

 7
Author: user1089583,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-28 13:23:40