Pourquoi est Parallèle.ForEach beaucoup plus vite alors AsParallel().ForAll() même si MSDN suggère le contraire?

J'ai fait quelques recherches pour voir comment nous pouvons créer une application multi-thread qui s'exécute par le biais d'un arbre.

De trouver comment cela peut être mis en œuvre de la meilleure façon que j'ai créé un test d'application qui s'exécute par le biais de mon C:\ disque et qui ouvre tous les répertoires.

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

Ce que j'ai rencontrés, cependant, est que lors de l'exécution de cette dans le mode 3 (Parallèle.ForEach) le code se termine dans environ 2,5 secondes (oui, j'ai un SSD 😉 ). L'exécution du code, sans parallélisation il termine en environ 8 secondes. Et l'exécution du code dans le mode 2 (AsParalle.ForAll ()), il faut une quasi-infinie quantité de temps.

Lors de la vérification dans le processus de l'explorateur j'ai aussi rencontrer quelques faits étranges:

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

Ce que je trouve particulièrement étrange, c'est qu'en Parallèle.ForEach semble ignorer tout parent threads/tâches qui sont toujours en cours d'exécution tandis que AsParallel().ForAll() semble attendre que la Tâche précédente soit terminée (ce qui n'est pas bientôt puisque tous les parents de Tâches sont toujours en attente de leur enfant tâches à accomplir).

Aussi ce que j'ai lu sur MSDN était: "Préférez ForAll ForEach Quand C'Est Possible"

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

Quelqu'un a une idée pourquoi cela pourrait être?

Edit 1:

Comme demandé par Matthew Watson, j'ai d'abord chargé de l'arbre en mémoire avant de faire la boucle à travers elle. Maintenant, le chargement de l'arbre se fait de manière séquentielle.

Les résultats sont cependant les mêmes. Unparallelized et Parallèles.ForEach maintenant compléter l'ensemble de l'arbre à environ 0,05 secondes, tandis que AsParallel().Pourtout encore ne va autour de 1 étape par seconde.

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:

Après la lecture de la mise à jour sur Matthieu commentaire, j'ai essayé d'ajouter le code suivant au programme:

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

Toutefois, cela ne change pas la façon dont le AsParallel peforms. Toujours les 8 premières étapes sont exécutées dans un instant, avant de ralentir à 1 étape /seconde.

(Note, je suis actuellement en ignorant les exceptions qui se produisent quand je ne peux pas accéder à un Répertoire par le bloc Try Catch autour de l'Annuaire.GetDirectories())

Edit 3:

Aussi ce que je m'intéresse principalement à la différence entre le Parallèle.ForEach et AsParallel.Pourtout parce que pour moi c'est juste étrange que, pour une raison quelconque, le second crée un Thread pour chaque récursion il le fait alors que le premier s'occupe de tout en autour de 30 fils max. (Et aussi pourquoi MSDN suggère d'utiliser le AsParallel même si elle crée tant de discussions avec un ~1 seconde de délai d'attente)

Edit 4:

Une autre chose étrange, j'ai trouvé:
Lorsque je tente de régler le MinThreads sur le pool de Threads au-dessus de 1023 il semble ignorer la valeur et de l'échelle de retour à environ 8 ou 16:
ThreadPool.SetMinThreads(1023, 16);

Encore lorsque j'utilise le 1023 il ne la première 1023 éléments très vite suivie par un retour à la lenteur avec laquelle j'ai eu l'expérience de tous les temps.

Note: littéralement plus de 1000 sujets sont maintenant créés (contre 30 pour l'ensemble de la Parallèle.ForEach un).

Est-ce à dire Parallèle.ForEach est juste plus intelligents dans les tâches de manipulation?

Quelques infos, ce code imprime deux fois 8 - 8 lorsque vous définissez la valeur au-dessus de 1023: (Lorsque vous définissez les valeurs à 1023 ou inférieur, il imprime la valeur correcte)

        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:

En tant que Doyen de la demande, j'ai créé un autre cas de créer manuellement des tâches:

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;

C'est aussi rapide qu'avec le Parallèle.ForEach() en boucle. Donc, nous n'avons toujours pas la réponse à pourquoi AsParallel().ForAll() est beaucoup plus lent.

  • Avec ThreadPool.SetMinThreads(4000, 4000); vous êtes réglage de l'IO port de terminaison des threads pour un nombre fou. Essayez ThreadPool.SetMinThreads(4000, 16); à la place (même pour SetMaxThreads())
  • Je l'ai fait maintenant, mais de toute façon toujours éprouver les mêmes résultats. Pour le Mode 2, je vois 1 fil supplémentaire popping up dans le moniteur de Ressources à chaque seconde. Aussi, quand j'permettre à la Console.WriteLine je le vois passer au travers de mon disque à environ 1 par seconde. Le Mode 1 et le 3, toujours exécuter l'ensemble de mon disque (80.173 éléments) en moins de 1 seconde (dans la mémoire de l'un)
  • Pouvez-vous utiliser une Tâche orientée vers la version – sens qui vous attendent à la mission d'un appel récursif, et de ne pas coup d'envoi d'une nouvelle tâche jusqu'à ce que l'appel est terminé? Le problème ici c'est que vous êtes rapidement inonder le nombre de threads à un grand nombre, quand vous avez quelque chose comme 4 cœurs du PROCESSEUR lorsque ce problème est en mémoire et CPU.
  • J'ai créé un 4e cas à tester votre solution, mais aussi découvert que c'est tout aussi rapide que l'Parallèle.ForEach. Je ne pense pas que cela a beaucoup à voir avec les opérations de mémoire et de CPU parce que je ne vois presque aucune activité du PROCESSEUR. Je pense que c'est plus lié à une sorte de délai d'attente quelque part dans le AsParallel().ForAll() la méthode.
  • C'est encore le coup d'envoi de tous les threads au démarrage, je pense. Je ne pensais pas ça exactement, mais je pensais que tu mettrais une attente de quelque sorte à l'intérieur de la boucle, de sorte que la boucle ne pas continuer jusqu'à ce que la récursivité est fait. Vous obtenez beaucoup de profondeur et peut-être plusieurs threads qu'il parcourt l'arborescence du répertoire, mais au moins il ne serait pas de créer tous les threads d'un seul coup. Vous pouvez également enquêter sur les fils à l'aide de Fil.Actuel.ID, peut-être, notez le nombre de threads sont créés avec chaque type de modèle.
  • Je sais qu'il n', mais c'est tout le problème. Faire de cette façon n'est pas le ralentir. Elle est très rapide. Uniquement en Mode 2 (avec AsParallel().ForAll() ralentit à l'étape 1 par seconde)
  • oui désolé, j'étais sur une tangente là. Mes observations avec les Tâches et les Parallèles.ForEach méthodes est qu'elles ne créer plus qu'une poignée de fils, généralement autour de 4 ou 8 avec l'hyperthreading sur un quad core de la machine. Encore une fois, je n'ai jamais osé s'aventurer dans un recusive enquête!
  • ouais c'est ce que je semble faire l'expérience aussi bien en Parallèle.ForEach et de nouvelles Tâches (même lors de l'utilisation de la récursivité). J'espère que quelqu'un ici peut expliquer le pourquoi et le comment AsParallel.Pourtout en diffère. (Et pourquoi Microsoft ne suggérons d'utiliser AsParallel.Pourtout)
  • Si vous voulez en Parallèle.Foreach dans un non le CPU, la tâche de l'utilisation MaxDegreeOfParallelism = num_of_cores il vous fera économiser des ressources et d'être plus rapides. dans le AsParallel.Pourtout il met en garde de ne pas l'utiliser avec IO des tâches limitées.
  • Aucun rapport avec la question, mais vous avez un problème avec fermeture cas 4 ci-dessus stackoverflow.com/questions/271440/...
  • Je vais corriger ça merci 🙂

InformationsquelleAutor Devedse | 2014-09-18