Pourquoi utiliser AsParallel () plus lentement que foreach dans ce cas?

J’extrais les données d’Excel dans ce format

  produit1 |  unnamedcol2 |  produit2 |  unnamedcol4 |  produit3 |  unnamedcol6 |  -------------------------------------------------- ----------------------------- @ 1foo |  1.10 |  @ 1foo |  0,3 |  @ 1foo |  0,3 @ 2foo |  1,00 |  @ 2foo |  2 |  @ 2foo |  @ 3foo |  1,52 |  @ 3foo |  2,53 |  @ 3foo |  @ 4foo |  1,47 |  |  |  @ 4foo |  1,31 @ 5foo |  1,49 |  |  |  @ 5foo |  1,31 

Le fichier utilise les 255 champs. En utilisant dapper-dot-net, je récupère les données via ce code

IEnumerable<IDictionary> excelDataRaw = conn.Query(ssortingng.Format("select * from {0}", table)).Cast<IDictionary>(); 

Je transmets ces données à ces méthodes de test. Les données sont renvoyées sous la forme d’un IEnumerable of IDictionaries où chaque clé est un produit et chaque valeur est un IDictionary où chaque clé est une valeur de la colonne produit et la valeur correspondante est une valeur de unnamedcol située à droite de la colonne de produit.

 var excelDataRefined = new List<IDictionary<string, IDictionary>>(); excelDataRefined.Add(new Dictionary<string, IDictionary>()); excelDataRefined[0].Add( "product", new Dictionary()); excelDataRefined[0]["product"].Add("@1foo", 1.1m); 

Les méthodes:

 private static Dictionary<string, IDictionary> Benchmark_foreach(IEnumerable<IDictionary> excelDataRaw) { Console.WriteLine("1. Using foreach"); var watch = new Stopwatch(); watch.Start(); List headers = excelDataRaw.Select(dictionary => dictionary.Keys).First().ToList(); bool isEven = false; List products = headers.Where(h => isEven = !isEven).ToList(); var dates = new List<IEnumerable>(); var prices = new List<IEnumerable>(); foreach (ssortingng field in headers) { ssortingng product1 = field; if (headers.IndexOf(field) % 2 == 0) { dates.Add( excelDataRaw.AsParallel().AsOrdered().Select(col => col[product1]).Where(row => row != null)); } if (headers.IndexOf(field) % 2 == 1) { prices.Add( excelDataRaw.AsParallel().AsOrdered().Select(col => col[product1] ?? 0m).Take(dates.Last().Count())); } } watch.Stop(); Console.WriteLine("Rearange the data in: {0}s", watch.Elapsed.TotalSeconds); watch.Restart(); var excelDataRefined = new Dictionary<string, IDictionary>(); foreach (IEnumerable datelist in dates) { decimal num; IEnumerable datelist1 = datelist; IEnumerable pricelist = prices[dates.IndexOf(datelist1)].Select(value => value ?? 0m).Where( content => decimal.TryParse(content.ToSsortingng(), out num)); Dictionary dict = datelist1.Zip(pricelist, (k, v) => new { k, v }).ToDictionary( x => (ssortingng)xk, x => decimal.Parse(xvToSsortingng())); if (!excelDataRefined.ContainsKey(products[dates.IndexOf(datelist1)])) { excelDataRefined.Add(products[dates.IndexOf(datelist1)], dict); } } watch.Stop(); Console.WriteLine("Zipped the data in: {0}s", watch.Elapsed.TotalSeconds); return excelDataRefined; } private static Dictionary<string, IDictionary> Benchmark_AsParallel(IEnumerable<IDictionary> excelDataRaw) { Console.WriteLine("2. Using AsParallel().AsOrdered().ForAll"); var watch = new Stopwatch(); watch.Start(); List headers = excelDataRaw.Select(dictionary => dictionary.Keys).First().ToList(); bool isEven = false; List products = headers.Where(h => isEven = !isEven).ToList(); var dates = new List<IEnumerable>(); var prices = new List<IEnumerable>(); headers.AsParallel().AsOrdered().ForAll( field => dates.Add( excelDataRaw.AsParallel().AsOrdered().TakeWhile(x => headers.IndexOf(field) % 2 == 0).Select( col => col[field]).Where(row => row != null).ToList())); headers.AsParallel().AsOrdered().ForAll( field => prices.Add( excelDataRaw.AsParallel().AsOrdered().TakeWhile(x => headers.IndexOf(field) % 2 == 1).Select( col => col[field] ?? 0m).Take(256).ToList())); dates.RemoveAll(x => x.Count() == 0); prices.RemoveAll(x => x.Count() == 0); watch.Stop(); Console.WriteLine("Rearange the data in: {0}s", watch.Elapsed.TotalSeconds); watch.Restart(); var excelDataRefined = new Dictionary<string, IDictionary>(); foreach (IEnumerable datelist in dates) { decimal num; IEnumerable datelist1 = datelist; IEnumerable pricelist = prices[dates.IndexOf(datelist1)].Select(value => value ?? 0m).Where( content => decimal.TryParse(content.ToSsortingng(), out num)); Dictionary dict = datelist1.Zip(pricelist, (k, v) => new { k, v }).ToDictionary( x => (ssortingng)xk, x => decimal.Parse(xvToSsortingng())); if (!excelDataRefined.ContainsKey(products[dates.IndexOf(datelist1)])) { excelDataRefined.Add(products[dates.IndexOf(datelist1)], dict); } } watch.Stop(); Console.WriteLine("Zipped the data in: {0}s", watch.Elapsed.TotalSeconds); return excelDataRefined; } private static Dictionary<string, IDictionary> Benchmark_ForEach(IEnumerable<IDictionary> excelDataRaw) { Console.WriteLine("3. Using ForEach"); var watch = new Stopwatch(); watch.Start(); List headers = excelDataRaw.Select(dictionary => dictionary.Keys).First().ToList(); bool isEven = false; List products = headers.Where(h => isEven = !isEven).ToList(); var dates = new List<IEnumerable>(); var prices = new List<IEnumerable>(); headers.ForEach( field => dates.Add( excelDataRaw.TakeWhile(x => headers.IndexOf(field) % 2 == 0).Select(col => col[field]).Where( row => row != null).ToList())); headers.ForEach( field => prices.Add( excelDataRaw.TakeWhile(x => headers.IndexOf(field) % 2 == 1).Select(col => col[field] ?? 0m). Take(256).ToList())); dates.RemoveAll(x => x.Count() == 0); prices.RemoveAll(x => x.Count() == 0); watch.Stop(); Console.WriteLine("Rearange the data in: {0}s", watch.Elapsed.TotalSeconds); watch.Restart(); var excelDataRefined = new Dictionary<string, IDictionary>(); foreach (IEnumerable datelist in dates) { decimal num; IEnumerable datelist1 = datelist; IEnumerable pricelist = prices[dates.IndexOf(datelist1)].Select(value => value ?? 0m).Where( content => decimal.TryParse(content.ToSsortingng(), out num)); Dictionary dict = datelist1.Zip(pricelist, (k, v) => new { k, v }).ToDictionary( x => (ssortingng)xk, x => decimal.Parse(xvToSsortingng())); if (!excelDataRefined.ContainsKey(products[dates.IndexOf(datelist1)])) { excelDataRefined.Add(products[dates.IndexOf(datelist1)], dict); } } watch.Stop(); Console.WriteLine("Zipped the data in: {0}s", watch.Elapsed.TotalSeconds); return excelDataRefined; } 
  • Benchmark_foreach a besoin d’une application. 3,5s pour réorganiser et 3s pour compresser les données.
  • Benchmark_AsParallel a besoin d’une application. 12s pour réorganiser et 0,005s pour compresser les données.
  • Benchmark_ForEach a besoin d’une application. 16s pour réorganiser et 0,005s pour compresser les données.

Pourquoi ça se comporte comme ça? Je m’attendais à ce que AsParallel soit le plus rapide car il s’exécute en parallèle plutôt que séquentiel. Ho puis-je optimiser cela?

Pour que le calcul en parallèle se produise, vous devez avoir plusieurs processeurs ou cœurs, sinon vous ne faites que mettre en queue des tâches du pool de threads en attente du processeur. Par exemple, AsParallel sur une seule machine principale est séquentiel, plus la surcharge de threadpool et le commutateur de contexte de thread. Même sur une machine à deux cœurs, il est possible que vous n’ayez pas les deux cœurs, car beaucoup d’autres choses fonctionnent sur la même machine.

Vraiment .AsParallel() ne devient utile que si vous avez de longues tâches avec des opérations de blocage (E / S) où le système d’exploitation peut suspendre le thread de blocage et en laisser exécuter un autre.

La création de threads supplémentaires et la gestion des charges de travail pour chacun de ces threads sont un travail fastidieux. Si vous avez un volume de travail limité, le temps système nécessaire pour créer les threads supplémentaires, basculer d’une tâche à l’autre, voler et redissortingbuer le travail entre les threads, etc. peut l’emporter sur les gains que vous obtenez en mettant en parallèle le travail en premier lieu. Vous voudrez peut-être profiler votre application pour savoir si vous êtes vraiment lié au processeur lorsque vous exécutez un seul processus. Si ce n’est pas le cas, il vaudra mieux garder le système à thread unique et votre goulot d’étranglement devient IO, ce qui n’est pas aussi facile à paralléliser.

Quelques recommandations supplémentaires: Vous allez voir une perte de performances en utilisant AsOrdered et TakeWhile, car ils doivent tous deux être synchronisés sur le thread d’origine. Envisagez de profiler sans passer de commande et voyez si cela offre une amélioration des performances.

En outre, envisagez d’utiliser un dictionnaire ConcurrentDictionary plutôt que le dictionnaire générique standard pour éviter les problèmes de concurrence d’access lors de l’ajout d’éléments.

Dans Benchmark_AsParallel et Benchmark_ForEach, vous effectuez une opération 2n dans Benchmark_foreach n.