2009-11-12 10 views
2

Question rapide:IEnumerable question: Meilleure performance?

Lequel est le plus rapide?

foreach (Object obj in Collection) 
{ 
    if(obj.Mandatory){ ... } 
} 

ou

foreach (Object obj in Collection.FindAll(o => o.Mandatory)) 
{ 
... 
} 

et si vous connaissez une suggestion plus rapide, je serais heureux de savoir.

Merci

+8

Pourquoi ne pas mesurer et découvrir? – Brian

+11

@Brian - ce n'est pas vraiment une attitude accueillante. –

+0

Je voulais juste l'éteindre et voir ce que les gens pensent. Merci quand même. –

Répondre

7

Le code de test suivant imprime le système tiques (1 tick = 100 nanosecondes) pour itérer 10 millions d'objets. Le FindAll est le plus lent et la boucle for est la plus rapide comme prévu.

Mais le surdébit de l'itération est mesuré en nanosecondes par article même dans le pire des cas. Si vous faites quelque chose de significatif dans la boucle (par exemple quelque chose qui prend une microseconde par élément), alors la différence de vitesse de l'itération est totalement insignifiant.

Alors, pour l'amour de Turing, ne l'interdisez pas dans vos directives de codage maintenant. Cela ne fait aucune différence pratique, et les instructions LINQ sont sûrement plus faciles à lire.

public class Test 
    { 
     public bool Bool { get; set; } 
    } 

    class Program 
    { 

     static void Main(string[] args) 
     { 
     // fill test list 
     var list = new List<Test>(); 
     for (int i=0; i<1e7; i++) 
     { 
      list.Add(new Test() { Bool = (i % 2 == 0) }); 
     } 

     // warm-up 
     int counter = 0; 
     DateTime start = DateTime.Now; 
     for (int i = 0; i < list.Count; i++) 
     { 
      if (list[i].Bool) 
      { 
       counter++; 
      } 
     } 

     // List.FindAll 
     counter = 0; 
     start = DateTime.Now; 
     foreach (var test in list.FindAll(x => x.Bool)) 
     { 
      counter++; 
     } 
     Console.WriteLine(DateTime.Now.Ticks - start.Ticks); // prints 7969158 

     // IEnumerable.Where 
     counter = 0; 
      start = DateTime.Now; 
     foreach (var test in list.Where(x => x.Bool)) 
     { 
      counter++; 
     } 
     Console.WriteLine(DateTime.Now.Ticks - start.Ticks); // prints 5156514 

     // for loop 
     counter = 0; 
     start = DateTime.Now; 
     for (int i = 0; i < list.Count; i++) 
     { 
      if (list[i].Bool) 
      { 
       counter++; 
      } 
     } 
     Console.WriteLine(DateTime.Now.Ticks - start.Ticks); // prints 2968902 


     } 
+0

Ceci est trompeur. Parfois, 'IEnumerable' est terriblement lent par rapport aux tableaux raw. Mais je suis d'accord, n'évitez pas "IEnumerable", c'est très élégant. Toujours profil et quand il est lent, et que vous utilisez 'IEnumerable', passez à un tableau ou' List'. Par exemple, je viens de corriger une boucle 'yield' en itérant un tableau' ListView' 'SelectedIndices' qui a piégé le CPU et gelé l'interface utilisateur pendant presque 2 secondes avant d'afficher le menu contextuel, avec plusieurs centaines d'éléments sélectionnés. Je l'ai changé pour utiliser '.CopyTo' un tableau suivi d'une ancienne boucle for avec l'index' int'. C'est instantané maintenant. – doug65536

16

Si votre Collection est un List<T> alors FindAll est mis en œuvre en créant une nouvelle List<T> et copier tous les éléments qui correspondent au prédicat. C'est évidemment plus lent que de simplement énumérer la collection et décider pour chaque élément si le prédicat est valide.

Si vous utilisez .NET 3.5, vous pouvez utiliser LINQ qui ne crée pas une copie et et est semblable à votre premier exemple:

foreach (object obj in someCollection.Where(o => o.Mandatory)) 
{ 
    ... 
} 

Noter ce n'est pas nécessairement la solution la plus rapide. Il est facile de voir qu'une méthode qui alloue de la mémoire et énumère une collection est plus lente qu'une méthode seulement énumère une collection. Si la performance est critique: mesurez-la.

+0

J'étais sur le point de poster exactement cela. Très bonne réponse. –

+0

merci. Je sentais que FindAll était plus lent, mais cela devrait être autre chose que de tester chaque élément. –

+0

Performance-sage, le foreach avec une clause if sera le plus rapide. En utilisant .Where sera légèrement plus lent, et en utilisant .FindAll sera * beaucoup * plus lent. –

3

le plus rapide que vous pourriez jamais obtenir sans parallélisation l'énumération en plusieurs threads en prenant compte de nombre de processeurs, etc:

for (int i = 0; i < Collection.Count; i++) 
{ 
    var item = Collection[i]; 
    if (item.Mandatory) { ... } 
} 

Je vous recommande bien de toujours utiliser LINQ au lieu d'écrire for ou foreach boucles parce que à l'avenir, il deviendra si intelligent qu'il sera capable de distribuer le travail sur les processeurs et de prendre en compte les choses spécifiques au matériel (voir PLinq) et il sera plus rapide que si vous écriviez vous-même les boucles: programmation déclarative vs impérative.

+2

Je ne suis pas convaincu que ce serait plus rapide. Vous devriez le profiler pour être sûr. Quoi qu'il en soit, la différence sera très petite. –

+4

A moins que quelque chose n'ait changé très récemment, car est un peu plus rapide que foreach, car vous ne créez pas un nouvel objet énumérateur et appelez ses méthodes au fur et à mesure. La différence est assez petite pour ne pas avoir d'importance à moins que votre code ne soit un endroit extrêmement critique. –

+2

Cette méthode n'est pas "la plus rapide que vous puissiez obtenir" - l'affectation à la variable locale (var item = Collection [i]) n'est pas nécessaire - il est plus rapide d'appeler simplement "if (Collection [i] .Mandatory) {.. .} "... –

6

Le premier sera un peu plus rapide.

Dans le deuxième cas, vous utilisez List<T>.FindAll pour créer une liste temporaire correspondant à vos critères. Ceci copie la liste, puis itère dessus.

Cependant, vous pouvez faire la même chose, avec la même vitesse que votre première option, en faisant:

foreach (Object obj in Collection.Where(o => o.Mandatory)) 
{ 
} 

C'est parce que Enumerable.Where utilise le streaming pour retourner un IEnumerable<T>, qui est généré comme vous itérer. Aucune copie n'est faite.

+0

Je suis d'accord avec ça.Where() est une méthode d'extension invoquée de manière statique dans la liste afin qu'aucun objet ne soit recréé. De plus, la boucle fonctionnera en utilisant un rendement de rendement pour chaque valeur individuelle. –

1

FindAll est juste du sucre syntaxique. Par exemple:

List<string> myStrings = new List<string>(); 
    foreach (string str in myStrings.FindAll(o => o.Length > 0)) 
    { 

    } 

Compile à:

List<string> list = new List<string>(); 
if (CS$<>9__CachedAnonymousMethodDelegate1 == null) 
{ 
    CS$<>9__CachedAnonymousMethodDelegate1 = new Predicate<string>(MyClass.<RunSnippet>b__0); 
} 
using (List<string>.Enumerator enumerator = list.FindAll(CS$<>9__CachedAnonymousMethodDelegate1).GetEnumerator()) 
{ 
    while (enumerator.MoveNext()) 
    { 
     string current = enumerator.Current; 
    } 
} 

public List<T> FindAll(Predicate<T> match) 
{ 
    if (match == null) 
    { 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); 
    } 
    List<T> list = new List<T>(); 
    for (int i = 0; i < this._size; i++) 
    { 
     if (match(this._items[i])) 
     { 
      list.Add(this._items[i]); 
     } 
    } 
    return list; 
} 

private static bool <RunSnippet>b__0(string o) 
{ 
    return (o.Length > 0); 
} 
0

Si la performance est en question, il est cependant probablement pas le goulot d'étranglement, avez-vous envisagé d'utiliser la bibliothèque parallèle ou PLINQ? voir ci-dessous:

Parallel.ForEach(Collection, obj => 
{ 
    if (obj.Mandatory) 
    { 
     DoWork(); 
    } 
}); 

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

En outre, bien qu'il semble peut-être un peu sans rapport comme si la performance jette un œil votre curiosité, si vous faites affaire avec des ensembles de données très volumineux, une recherche binaire peut être utile. Dans mon cas, j'ai deux listes de données distinctes. Je dois traiter avec des listes de millions d'enregistrements et cela m'a sauvé littéralement une quantité exponentielle de temps par exécution. Le seul inconvénient est qu'il est seulement utile pour les très grandes collections et doit être trié à l'avance. Vous remarquerez également que cela fait usage de la classe ConcurrentDictionary, qui fournit un surdébit significatif, mais elle est thread-safe et était nécessaire en raison des exigences et du nombre de threads que je gère de manière asynchrone.

private ConcurrentDictionary<string, string> items; 
private List<string> HashedListSource { get; set; } 
private List<string> HashedListTarget { get; set; } 

this.HashedListTarget.Sort(); 
this.items.OrderBy(x => x.Value); 

private void SetDifferences() 
{ 
    for (int i = 0; i < this.HashedListSource.Count; i++) 
    { 
     if (this.HashedListTarget.BinarySearch(this.HashedListSource[i]) < 0) 
     { 
      this.Mismatch.Add(items.ElementAt(i).Key); 
     } 
    } 
} 

Example displaying the benefits of using Binary Search Cette image a été publiée dans un article trouvé ici: http://letsalgorithm.blogspot.com/2012/02/intersecting-two-sorted-integer-arrays.html

Hope this helps!