Warum ist Parallel.ForEach viel schneller dann AsParallel().ForAll (), obwohl MSDN anderes vermuten?

Ich mache ein paar Untersuchungen um zu sehen, wie wir können, erstellen Sie eine Multithread-Anwendung, die durch einen Baum.

Zu finden, wie diese umgesetzt werden können, in der beste Weg, ich habe eine test-Anwendung, die läuft durch meine C:\ Festplatte und öffnet alle Verzeichnisse.

class Program
{
    static void Main(string[] args)
    {
        //var startDirectory = @"C:\The folder\RecursiveFolder";
        var startDirectory = @"C:\";

        var w = Stopwatch.StartNew();

        ThisIsARecursiveFunction(startDirectory);

        Console.WriteLine("Elapsed seconds: " + w.Elapsed.TotalSeconds);

        Console.ReadKey();
    }

    public static void ThisIsARecursiveFunction(String currentDirectory)
    {
        var lastBit = Path.GetFileName(currentDirectory);
        var depth = currentDirectory.Count(t => t == '\\');
        //Console.WriteLine(depth + ": " + currentDirectory);

        try
        {
            var children = Directory.GetDirectories(currentDirectory);

            //Edit this mode to switch what way of parallelization it should use
            int mode = 3;

            switch (mode)
            {
                case 1:
                    foreach (var child in children)
                    {
                        ThisIsARecursiveFunction(child);
                    }
                    break;
                case 2:
                    children.AsParallel().ForAll(t =>
                    {
                        ThisIsARecursiveFunction(t);
                    });
                    break;
                case 3:
                    Parallel.ForEach(children, t =>
                    {
                        ThisIsARecursiveFunction(t);
                    });
                    break;
                default:
                    break;
            }

        }
        catch (Exception eee)
        {
            //Exception might occur for directories that can't be accessed.
        }
    }
}

Was ich je erlebt habe ist jedoch, dass bei der Ausführung dieser im Modus 3 (Parallel -.ForEach) der code abgeschlossen in rund 2,5 Sekunden (ja ich habe eine SSD 😉 ). Der code ausgeführt wird ohne Parallelisierung, die es vervollständigt in rund 8 Sekunden. Und der code ausgeführt wird im Modus 2 (AsParalle.ForAll()) dauert es eine nahezu unendliche Menge an Zeit.

Beim Check-in Prozess explorer I auch auftreten, ein paar seltsame Fakten:

Mode1 (No Parallelization):
Cpu:     ~25%
Threads: 3
Time to complete: ~8 seconds

Mode2 (AsParallel().ForAll()):
Cpu:     ~0%
Threads: Increasing by one per second (I find this strange since it seems to be waiting on the other threads to complete or a second timeout.)
Time to complete: 1 second per node so about 3 days???

Mode3 (Parallel.ForEach()):
Cpu:     100%
Threads: At most 29-30
Time to complete: ~2.5 seconds

Was ich besonders seltsam ist, dass Parallel.ForEach scheint zu ignorieren, die übergeordneten threads/tasks, die ausgeführt werden, während AsParallel().ForAll() scheint zu warten, bis der Vorherige Task entweder komplett (was so schnell nicht, da alle übergeordneten Aufgaben warten noch auf Ihr Kind Aufgaben zu vervollständigen).

Auch das, was ich Lesen Sie auf der MSDN-Website wurde: "Lieber ForAll zu ForEach, Wenn Es Möglich Ist"

Quelle: http://msdn.microsoft.com/en-us/library/dd997403(v=vs. 110).aspx

Hat jemand eine Ahnung, warum dies sein könnte?

Edit 1:

Anfrage von Matthew Watson ich habe zum ersten mal geladen wird der Baum in den Arbeitsspeicher, bevor die Schleife durch Sie. Nun die Belastung der Struktur erfolgt sequentiell.

Die Resultate jedoch sind die gleichen. Unparallelized und Parallel.ForEach schließen Sie nun den ganzen Baum in etwa 0,05 Sekunden, während AsParallel().ForAll noch geht nur um 1 Schritt pro Sekunde.

Code:

class Program
{
    private static DirWithSubDirs RootDir;

    static void Main(string[] args)
    {
        //var startDirectory = @"C:\The folder\RecursiveFolder";
        var startDirectory = @"C:\";

        Console.WriteLine("Loading file system into memory...");
        RootDir = new DirWithSubDirs(startDirectory);
        Console.WriteLine("Done");


        var w = Stopwatch.StartNew();

        ThisIsARecursiveFunctionInMemory(RootDir);

        Console.WriteLine("Elapsed seconds: " + w.Elapsed.TotalSeconds);

        Console.ReadKey();
    }        

    public static void ThisIsARecursiveFunctionInMemory(DirWithSubDirs currentDirectory)
    {
        var depth = currentDirectory.Path.Count(t => t == '\\');
        Console.WriteLine(depth + ": " + currentDirectory.Path);

        var children = currentDirectory.SubDirs;

        //Edit this mode to switch what way of parallelization it should use
        int mode = 2;

        switch (mode)
        {
            case 1:
                foreach (var child in children)
                {
                    ThisIsARecursiveFunctionInMemory(child);
                }
                break;
            case 2:
                children.AsParallel().ForAll(t =>
                {
                    ThisIsARecursiveFunctionInMemory(t);
                });
                break;
            case 3:
                Parallel.ForEach(children, t =>
                {
                    ThisIsARecursiveFunctionInMemory(t);
                });
                break;
            default:
                break;
        }
    }
}

class DirWithSubDirs
{
    public List<DirWithSubDirs> SubDirs = new List<DirWithSubDirs>();
    public String Path { get; private set; }

    public DirWithSubDirs(String path)
    {
        this.Path = path;
        try
        {
            SubDirs = Directory.GetDirectories(path).Select(t => new DirWithSubDirs(t)).ToList();
        }
        catch (Exception eee)
        {
            //Ignore directories that can't be accessed
        }
    }
}

Edit 2:

Nach dem Lesen der update auf das Matthäus-Kommentar, ich habe versucht, fügen Sie den folgenden code, um das Programm:

ThreadPool.SetMinThreads(4000, 16);
ThreadPool.SetMaxThreads(4000, 16);

Diese jedoch nicht ändern, wie die AsParallel peforms. Immer noch die ersten 8 Schritte ausgeführt werden, in einem Augenblick, bevor Verlangsamung um 1 Schritt /Sekunde.

(Extra Hinweis, ich bin derzeit ignorieren die Ausnahmen, die auftreten, wenn ich keinen Zugriff auf ein Verzeichnis durch die Try-Catch-block um das Verzeichnis.GetDirectories())

Edit 3:

Auch das, was ich bin vor allem interessiert, ist der Unterschied zwischen Parallel.ForEach und AsParallel.Füralle, weil für mich ist es nur seltsam, dass aus irgendeinem Grund die zweite erzeugt einen Thread für jede Rekursion es tut, während die erste einmal verarbeitet, alles in rund 30 threads max. (Und auch, warum MSDN empfiehlt die AsParallel obwohl es schafft so viel threads mit ~1 Sekunde timeout)

Edit 4:

Andere seltsame Sache, die ich herausgefunden:
Wenn ich versuche, um die MinThreads auf den Thread-pool oberhalb von 1023 scheint es zu ignorieren, den Wert und die Skala wieder um 8-oder 16:
ThreadPool.SetMinThreads - (1023, 16);

Immer noch, wenn ich 1023 tut es die ersten 1023 Elemente sehr schnell folgte, gehen Sie wieder auf das langsame Tempo habe ich schon erlebt, die ganze Zeit.

Hinweis: Auch buchstäblich mehr als 1000 threads erstellt (im Vergleich zu 30 für die ganze Parallele.ForEach-one).

Bedeutet dies Parallel.ForEach ist einfach viel schlauer im Umgang mit Aufgaben?

Paar mehr Infos, dieser code druckt die zweimal 8 - 8 wenn Sie den Wert über 1023: (Wenn Sie die Werte bis 1023 oder niedriger, es gibt den richtigen Wert)

        int threadsMin;
        int completionMin;
        ThreadPool.GetMinThreads(out threadsMin, out completionMin);
        Console.WriteLine("Cur min threads: " + threadsMin + " and the other thing: " + completionMin);

        ThreadPool.SetMinThreads(1023, 16);
        ThreadPool.SetMaxThreads(1023, 16);

        ThreadPool.GetMinThreads(out threadsMin, out completionMin);
        Console.WriteLine("Now min threads: " + threadsMin + " and the other thing: " + completionMin);

Edit 5:

Als der Dekan den Antrag habe ich einen anderen Fall manuell erstellen Aufgaben:

case 4:
    var taskList = new List<Task>();
    foreach (var todo in children)
    {
        var itemTodo = todo;
        taskList.Add(Task.Run(() => ThisIsARecursiveFunctionInMemory(itemTodo)));
    }
    Task.WaitAll(taskList.ToArray());
    break;

Dieser ist auch schneller als die Parallel.ForEach () - Schleife. So haben wir immer noch nicht die Antwort, warum AsParallel().ForAll() ist so viel langsamer.

  • Mit ThreadPool.SetMinThreads(4000, 4000); Sie die Einstellung der IO-completion-port-threads, um eine wahnsinnige Anzahl. Versuchen ThreadPool.SetMinThreads(4000, 16); statt (gleich für SetMaxThreads())
  • Ich habe getan, dass jetzt aber irgendwie immer noch die gleichen Ergebnisse. Für Mode 2 sehe ich 1 extra-thread, oben knallend in den Ressourcen-monitor, jede Sekunde. Auch wenn ich Sie aktivieren die Konsole.WriteLine ich es sehe, bewegt sich durch meine Festplatte auf über 1 Stufe pro Sekunde. Modus 1 und 3 noch ausführen, meine ganze Festplatte (80.173-Elemente) in viel weniger als 1 Sekunde (die im Gedächtnis)
  • Sie können verwenden Sie einen Task-orientierten version – was bedeutet, dass Sie erwarten, die Aufgabe, einen rekursiven Aufruf, und nicht die kick-off-eine neue Aufgabe, bis der Anruf beendet wurde? Das problem hier ist, dass Sie schnell überschwemmen die Anzahl der threads, um eine große Zahl, wenn Sie nur so etwas wie 4 CPU-Kerne, wenn dieses problem ist in-memory-und CPU-gebunden.
  • Ich habe eine 4. Fall testen Sie Ihre Lösung fanden aber auch heraus, dass diese genauso schnell wie Parallel.ForEach. Ich glaube nicht, es hat viel mit Erinnerung zu tun-Operationen und CPU, da sehe ich fast keine CPU-Aktivität. Ich denke, es ist eher mit einer Art timeout irgendwo in der AsParallel().ForAll () - Methode.
  • Das ist immer noch kicking off alle threads, die beim Start, denke ich. Ich habe nicht gedacht, dass dieser genau, aber ich dachte, Sie würde eine Wartezeit von einer Art innerhalb der for-Schleife, sodass die Schleife wird nicht fortgesetzt, bis die Rekursion erfolgt. Sie bekommen eine Menge von Tiefe und vielleicht mehrere threads, wie es geht nach unten das Verzeichnis, aber zumindest wäre es nicht schaffen, alle threads in einem Rutsch. Sie können auch untersuchen, die threads mit Thread.Aktuelle.ID, vielleicht aufzeichnen, wie viele threads erstellt werden, mit jeder Art von Modell.
  • Ich weiß, es tut, aber das ist das ganze problem. Doing es auf diese Weise nicht zu verlangsamen. Es ist wirklich schnell. Nur Modus 2 (mit AsParallel().ForAll() verlangsamt, um 1 Schritt pro Sekunde)
  • ja sorry, ich war off auf einer Tangente, die es gibt. Meine Beobachtungen mit den Aufgaben und mit den Parallelen.ForEach-Methoden ist, dass Sie nie mehr als eine Handvoll threads, in der Regel etwa 4 oder 8 mit hyperthreading auf einem quad-core-Maschine. Dann wieder, ich habe nie gewagt, Wagen, in eine recusive Untersuchung!
  • yeah das ist, was ich scheinen zu erleben, als auch für Parallel.ForEach und neue Aufgaben (auch wenn Sie mit Rekursion). Ich hoffe das hier jemand erklären kann, warum und wie AsParallel.ForAll unterscheidet sich von Ihnen. (Und warum Microsoft nicht empfehlen die Verwendung von AsParallel.ForAll)
  • Wenn Sie möchten, Parallel.Foreach in ein nicht CPU-bound tasks verwenden MaxDegreeOfParallelism = num_of_cores es wird Ressourcen zu sparen und schneller sein. in der AsParallel.ForAll warnt es nicht zu verwenden, es mit IO-bound-Aufgaben.
  • Unabhängig von der Frage, aber Sie haben eine closure problem mit Fall 4 oben stackoverflow.com/questions/271440/...
  • Ich werde es zu beheben, danke 🙂

InformationsquelleAutor Devedse | 2014-09-18
Schreibe einen Kommentar