Parallel.ForEach ve Task.Factory.StartYeni


267

Aşağıdaki kod snippet'leri arasındaki fark nedir? Her ikisi de threadpool dişlerini kullanmayacak mı?

Örneğin, bir koleksiyondaki her öğe için bir işlev çağırmak istersem,

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

vs

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

Yanıtlar:


302

Birincisi çok daha iyi bir seçenektir.

Paralel.ForEach, dahili olarak, Partitioner<T>koleksiyonunuzu iş öğelerine dağıtmak için a kullanır . Öğe başına bir görev yapmaz, bunun yerine ilgili ek yükü azaltmak için bunu toplu olarak işler.

İkinci seçenek, Taskkoleksiyonunuzdaki her öğe için bir tane zamanlayacaktır . Sonuçlar (neredeyse) aynı olsa da, bu özellikle büyük koleksiyonlar için gerekenden çok daha fazla yük getirecek ve genel çalışma sürelerinin daha yavaş olmasına neden olacaktır.

FYI - Kullanılan Partitioner , istenirse Parallel.ForEach için uygun aşırı yükler kullanılarak kontrol edilebilir . Ayrıntılar için bkz . MSDN'deki Özel Bölümleyiciler.

Çalışma zamanında temel fark, ikincisinin eşzamansız davranmasıdır. Bu, Parallel.ForEach kullanılarak şu şekilde çoğaltılabilir:

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

Bunu yaparak, bölücülerden yararlanmaya devam edersiniz, ancak işlem tamamlanana kadar engellemeyin.


8
Parallel.ForEach tarafından yapılan varsayılan bölümleme olan IIRC, mevcut donanım iş parçacığı sayısını da dikkate alarak, başlamak için optimum Görev sayısını çalışmak zorunda kalmamanızı sağlar. Microsoft'un Paralel Programlama Kalıpları makalesine göz atın ; içindeki tüm bu şeylerin harika açıklamaları var.
Mal Ross

2
@Mal: Bir çeşit ... Bu aslında Partitioner değil, daha çok TaskScheduler'ın işi. TaskScheduler varsayılan olarak bunu şimdi çok iyi işleyen yeni ThreadPool'u kullanıyor.
Reed Copsey

Teşekkürler. "Uzman değilim ama ..." uyarısında kalmam gerektiğini biliyordum. :)
Mal Ross

@ReedCopsey: Parallel.ForEach ile başlayan görevleri sarmalayıcı görevine nasıl eklerim? Böylece bir sarmalayıcı görevinde .Wait () öğesini çağırdığınızda, paralel çalışan görevler tamamlanana kadar askıda kalıyor mu?
Konstantin Tarkus

1
@Tarkus Birden fazla istekte bulunuyorsanız, yalnızca her bir iş öğesinde (Paralel döngünüzde) HttpClient.GetString'i kullanmanız daha iyi olur. Zaten eşzamanlı döngünün içine bir eşzamansız seçenek koymak için hiçbir neden, genellikle ...
Reed Copsey

89

"Parallel.For" ve "Task" nesneleri ile bir "1.000.000.000 (bir milyar)" yöntemi çalıştırmak için küçük bir deneme yaptım.

İşlemci süresini ölçtüm ve Paralel'i daha verimli buldum. Görevinizi küçük iş öğelerine ayırır ve bunları tüm çekirdeklerde paralel olarak en uygun şekilde yürütür. Çok sayıda görev nesnesi oluştururken (FYI TPL, iş parçacığı havuzunu dahili olarak kullanacaktır), her deneyde her bir yürütmeyi, aşağıdaki deneyden belirgin olan kutuda daha fazla stres yaratarak hareket ettirecektir.

Ayrıca temel TPL açıklar ve aynı zamanda Parallel.For daha verimli iç kısmı kullanır nasıl gösterdi küçük bir video oluşturduk http://www.youtube.com/watch?v=No7QqSc5cl8 normal görevler ve iş parçacığı ile karşılaştırıldığında.

Deney 1

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

Deney 2

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

İşlemci zaman karşılaştırması


Daha verimli olurdu ve iş parçacığı oluşturmanın maliyetli olmasının nedeni Deneme 2 çok kötü bir uygulamadır.
Tim

@ Georgi-lütfen neyin kötü olduğu hakkında daha fazla konuşmaya özen gösterin.
Shivprasad Koirala

3
Üzgünüm, hatam, açıklığa kavuşmalıydım. Demek istediğim, 1000000000'e kadar bir döngüde Görevlerin yaratılması. Tepegöz düşünülemez. Paralel'in aynı anda 63'ten fazla görev oluşturamayacağından bahsetmiyoruz, bu da durumda çok daha optimize edilmiştir.
Georgi-it

Bu 1000000000 görev için geçerlidir. Ancak bir görüntüyü (tekrar tekrar, yakınlaştırma fraktal) işlediğimde ve Paralel yaptığımda. Satırlarda, son iş parçacıklarının bitmesini beklerken çekirdeklerin birçoğu boştadır. Daha hızlı hale getirmek için verileri 64 çalışma paketine ayırdım ve bunun için görevler oluşturdum. (Sonra Görev.TekrarTüm tamamlanmayı beklemek.) Fikir, boş iş parçacıklarının (Parallel.For) atanmış yığınlarını bitirmesini beklemek yerine işin bitmesine yardımcı olmak için bir iş paketi almasını sağlamaktır.
Tedd Hansen

1
Mehthod1()Bu örnekte ne yapar ?
Zapnologica

17

Parallel.ForEach, döngü tamamlanıncaya kadar optimizasyon yapar (hatta yeni iş parçacıkları başlatmayabilir) ve engeller ve Task.Factory her öğe için açıkça yeni bir görev örneği oluşturur ve tamamlanmadan önce geri döner (eşzamansız görevler). Paralel. Foreach çok daha verimlidir.


11

Bana göre en gerçekçi senaryo, görevlerin tamamlanması gereken ağır bir işlem olduğunda. Shivprasad'ın yaklaşımı, bilgi işlemden çok nesne oluşturma / bellek tahsisine odaklanır. Aşağıdaki yöntemi çağıran bir araştırma yaptım:

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

Bu yöntemin yürütülmesi yaklaşık 0.5 saniye sürer.

Paralel kullanarak 200 kez aradım:

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

Sonra eski moda yolu kullanarak 200 kez aradım:

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

İlk vaka 26656ms, ikincisi 24478ms'de tamamlandı. Birçok kez tekrarladım. Her seferinde ikinci yaklaşım marjinal olarak daha hızlıdır.


Parallel.For kullanmak eski moda bir yoldur. Tek tip olmayan iş birimleri için Görev kullanılması önerilir. Microsoft MVP'leri ve TPL tasarımcıları da Görevleri kullanmanın iş parçacıklarını daha verimli kullanacağından, diğer birimlerin tamamlanmasını beklerken ienot bloğundan daha fazla yararlanacağından bahsetmektedir.
Suncat2000
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.