Accueil > .Net, C# > [C#4] Parallélisme – Episode 2

[C#4] Parallélisme – Episode 2

Le parallélisme, c’est bon, c’est bien, c’est magnifique (prononcer mag-ni-fique).

J’ai parlé hier de mes 94k lignes à remonter. C’est des pièces jointes et faut que je les enregistre (copie) autre part. Les chemins sont tous stockés en base.

Premier jet : je récupère mes lignes, je les stocke dans une liste d’objets FileToSave (cf. billet d’hier) et je travaille dessus, voir je fais les deux en une seule boucle.
Ca donne 140 secondes pour 350 fichiers, soit 2.5 fichiers / secondes.
En extrapolant, avec mes 94.333 fichiers, ça donne 37.733 secondes, soit ~629 minutes, soit ~10 heures de traitements… Pas cool, ça…

Alors, comment procéder ?

Ma première pensée a été de prendre des blocs plus petits.
En regardant les données, mes pièces jointes sont de deux types.
Le premier type comprend 3078 pièces jointes. Avec un bloc de cette taille, je reprends grosso modo la même méthode qu’hier.

Ce qui me laisse un bloc de 91.255 pièces jointes.
Celles-ci, je dois les copier dans des répertoires, par années. J’ai 13 années (1999 -> 2011).
En décomposant ma requête principale par année, ma plus grosse année comprend 13k pièces jointes.

Donc, je procède comment ?
Voici le descriptif :

  • récupération des années, en BDD
  • pour chaque année
  •           créer une entrée dans un dictionnaire (la clef est l’année), la valeur dans le dictionnaire est une liste
  •           récupérer les pièces jointes de l’année, en BDD
  •           pour chaque pièce jointe
  •                    ajouter la pièce jointe dans la liste de l’année

Avec le parallélisme, ça donne quoi ?

ConcurrentDictionary<string, BlockingCollection<FileToSave>> pjs = new ConcurrentDictionary<string, BlockingCollection<FileToSave>>();
// Récupération des différentes années
dt = dal.ExecuteCommand Queries.YEARS_FOR_PJs);
// Pour chaque année
Parallel.ForEach(dt.AsEnumerable(), row =>
{
    string year = row["FILEYEAR"].ToString();
    // Création de la liste sur l'année courante
    pjs.TryAdd(year, new BlockingCollection<FileToSave>());
    // Récupération des PJs sur l'année courante (pour la DAL, je l’ai pas encore refaite…)
    DataTable innerDt = new GenericDal(dal.ConnectionString).ExecuteCommand(
        String.Format(Queries.PJs_By_YEAR, year));
    // Pour chaque pièce jointe
    Parallel.ForEach(innerDt.AsEnumerable(), innerRow =>
    {
        // On ajoute la pièce jointe à la liste des PJs de l'année courante
        pjs[year].Add(
            new FileToSave()
            {
                Type = FileType.FileSystem,
                CurrentPath = innerRow["FILEPATH"].ToString(),
                TargetName = innerRow["FILENAME"].ToString(),
                TargetPath = Path.Combine(basePath, year)
            });
    });
    // Le traitement sur l'année est achevée, on affiche le nombre de PJs pour faciliter le contrôle
    Console.WriteLine("==> Année {0} achevée avec {1} fichiers.", year, pjs[year].Count);
});

Sur la console, j’obtiens ceci :

Parallelisme Affichage de la console

Donc, là, j’ai un dictionnaire parfaitement exploitable et 12 secondes, si c’est pas magique, ça !!!

Après, comment je le traite ?

Parallel.ForEach(pjs.Keys, key =>
{
    i = 0;
    Parallel.ForEach(pjs[key], item =>
    {
        if (i++ % 100 == 0)
        {
            Console.WriteLine("Année {0}, fichier #{1} / {2} : {3}", key, i, pjs[key].Count, timerPJs.ElapsedMilliseconds);
        }
        item.SaveFile(dal);
    });
});

Et là, la console me dit que, en 74 secondes, j’ai copié 1379 fichiers pour un total de 681Mo.
Autrement dit, 18 fichiers par secondes. En extrapolant, ça fait 85 minutes pour mes 91k fichiers.

Grâce au parallélisme, je suis donc passé d’un temps théorique de 10h à un temps théorique de 2h. Y a de quoi être content, non ?🙂

Sinon, pourquoi deux boucles ?
Principalement parce que j’ai constaté que si je fais les 2 opérations en même temps, c’est bien plus long (de l’ordre de x2 sur mon poste). De plus, c’est deux opérations bien distinctes, pour moi : récupération des données, copie des fichiers, ce qui veut dire qu’elles peuvent ne pas être faites successivement (ou, pour les tests, j’ai parfois besoin QUE la récupération, histoire de vérifier que j’ai bien tous mes fichiers, par exemple).

Pour ma machine, c’est un Intel Core 2 CPU, 1.86GHz, 3.25Go de RAM, donc c’est pas non plus une bête de course !

Mais, pour le coup, lors des traitements, les 2 cœurs…sont assez paisibles.

Parallelisme Utilisation CPU

Parallelisme Utilisation CPU

La marque rouge indique le début du traitement, j’ai une pause entre la récupération des données et la copie des fichiers. Au total, 500 ont été copiés pour cet exemple (le dernier pic sur le bord droit est l’arrêt du test).

Catégories :.Net, C#
  1. Aucun commentaire pour l’instant.
  1. 17/06/2011 à 20:57

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :