2014-09-03 3 views
4

J'ai fait une fonction pour trouver récursivement le premier élément ou par défaut qui correspond à une condition (premier bloc de code). Resharper m'a suggéré de changer quelques lignes dans une seule ligne LINQ (deuxième bloc de code).D'où vient cette performance LINQ?

Je me demandais si la suggestion Resharper me donnerait la même performance et la même empreinte mémoire. J'ai fait un test pour la performance (3ème bloc de code). Le résultat est exactement ce à quoi je m'attendais. Pourquoi la différence est si grande?

8156 milliseconds 
Laure 
23567 milliseconds 
Laure LINQ 

D'où vient cette différence ??? Pourquoi les résultats ne sont-ils pas les mêmes? ... ou du moins, plus proches?

public static T RecursiveFirstOrDefault<T>(this T item, Func<T, IEnumerable<T>> childrenSelector, Predicate<T> condition) 
    where T : class // Hierarchy implies class. Don't need to play with "default()" here. 
{ 
    if (item == null) 
    { 
     return null; 
    } 

    if (condition(item)) 
    { 
     return item; 
    } 

    foreach (T child in childrenSelector(item)) 
    { 
     T result = child.RecursiveFirstOrDefault(childrenSelector, condition); 
     if (result != null) 
     { 
      return result; 
     } 
    } 

    return null; 
} 

Mais ReSharper m'a suggéré de convertir le bloc foreach à une requête LINQ comme suit:

public static T RecursiveFirstOrDefaultLinq<T>(this T item, Func<T, IEnumerable<T>> childrenSelector, Predicate<T> condition) 
    where T : class // Hierarchy implies class. Don't need to play with "default()" here. 
{ 
    if (item == null) 
    { 
     return null; 
    } 

    if (condition(item)) 
    { 
     return item; 
    } 

    // Resharper change: 
    return childrenSelector(item).Select(child => child.RecursiveFirstOrDefaultLinq(childrenSelector, condition)).FirstOrDefault(result => result != null); 
} 

Test:

private void ButtonTest_OnClick(object sender, RoutedEventArgs e) 
{ 
    VariationSet varSetResult; 
    Stopwatch watch = new Stopwatch(); 

    varSetResult = null; 
    watch.Start(); 
    for(int n = 0; n < 10000000; n++) 
    { 
     varSetResult = Model.VariationRef.VariationSet.RecursiveFirstOrDefault((varSet) => varSet.VariationSets, 
      (varSet) => varSet.Name.Contains("Laure")); 
    } 
    watch.Stop(); 
    Console.WriteLine(watch.ElapsedMilliseconds.ToString() + " milliseconds"); 
    Console.WriteLine(varSetResult.Name); 

    watch.Reset(); 

    varSetResult = null; 
    watch.Start(); 
    for(int n = 0; n < 10000000; n++) 
    { 
     varSetResult = Model.VariationRef.VariationSet.RecursiveFirstOrDefaultLinq((varSet) => varSet.VariationSets, 
      (varSet) => varSet.Name.Contains("Laure")); 
    } 
    watch.Stop(); 
    Console.WriteLine(watch.ElapsedMilliseconds.ToString() + " milliseconds"); 
    Console.WriteLine(varSetResult.Name + " LINQ"); 

} 

Je dois aller aujourd'hui ... L'espoir de répondre correctement sur les tests: x86, sortie sur une machine 12 de base, windows 7, 4.5 Framework.Net,

Ma conclusion:

Dans mon cas, il est ~ 3x plus rapide dans la version non linq. La lisibilité est meilleure dans LINQ mais qui se soucient QUAND c'est dans une bibliothèque où vous devriez seulement vous rappeler ce qu'il fait et comment l'appeler (dans ce cas - pas un cas absolu général). LINQ est presque toujours plus lent qu'une bonne méthode codée. Je PERSONNELLEMENT saveur:

  • LINQ: Lorsque le rendement est pas vraiment un problème (la plupart des cas) dans code de projet spécifique
  • non Linq: Lorsque la performance est un problème dans le code de projet spécifique, et lorsqu'il est utilisé Une bibliothèque et où le code devrait être stable et fixe où l'utilisation de la méthode devrait être bien documentée et nous ne devrait pas vraiment besoin de creuser à l'intérieur.
+0

Le compilateur sous-jacent compile le code LINQ radicalement différent de code foreach, c'est là votre différence vient. –

+7

Vous ne devez pas réinitialiser la minuterie pour obtenir le nombre correct pour la deuxième itération de test? – entropic

+0

@entropic, merci ... :-(!!!!! –

Répondre

3

Voici quelques raisons pour lesquelles il y a une différence entre les non-LINQ et la performance du code LINQ:

  1. Chaque appel à une méthode a une surcharge de performance. L'information doit être poussé sur la pile, la CPU doit passer à une ligne d'instruction différente, etc. Dans la version LINQ vous appelez dans Select et FirstOrDefault, que vous ne faites pas dans la version non-LINQ.
  2. Lorsque vous créez un Func<> pour passer à la méthode Select, le temps système et la mémoire sont surchargés. La surcharge de mémoire, lorsqu'elle est multipliée plusieurs fois comme vous le faites dans votre test de performances, peut entraîner le besoin d'exécuter le récupérateur de mémoire plus souvent, ce qui peut être lent.
  3. La méthode Sélectionnez LINQ vous appelez produit un objet qui représente sa valeur de retour. Cela ajoute également un peu de consommation de mémoire.

Pourquoi la différence est-elle si grande?

Il est en fait pas grand. Il est vrai que LINQ prend 50% plus, mais honnêtement vous parlez de ne pouvoir terminer cette opération récursive ensemble 400 fois dans une milliseconde. Ce n'est pas lent, et vous ne remarquerez probablement pas la différence à moins que ce soit une opération que vous faites tout le temps dans une application de haute performance comme un jeu vidéo.

+0

D'abord ... Merci beaucoup :-). A propos de "1.", je suis d'accord qu'il a un impact, je soupçonne que ce n'est pas la grande partie de l'époque. A propos de "2", je ne suis pas sûr de comprendre parce que je le vois, c'est la même chose pour les deux cas. A propos de "3", je pense que vous avez probablement mis le doigt dessus car il faut créer un nouvel objet qui dans ce cas est significatif par rapport au code entier, considérant aussi que chacun des objets de la hiérarchie (dans mon cas de test) une condition très rapide à vérifier. Merci beaucoup! –

+0

J'ai fait une autre correction que personne ne voyait vraiment et qui était un énorme bug ... Dans ma version non linq, j'appelais la version Linq. Les résultats sont mis à jour aussi et maintenant il est presque 3 fois plus rapide en n'utilisant pas LINQ qui est le même que ce que je vois dans Magnus dans les commentaires de ma question. –

+0

@EricOuellet: En ce qui concerne # 2: Chaque fois que vous appelez '.Choisir()', vous passez dans un délégué ('enfant => child.RecursiveFirstOrDefaultLinq (childrenSelector, état)'), qui se ferme sur deux variables. Même si vous ne dites jamais "nouveau", ce délégué est instancié automatiquement, fondamentalement le même que si vous aviez une classe avec deux champs dessus. Donc, la création d'un objet, plus la nécessité de ramasser les ordures plus tard, aura un impact sur les performances. – StriplingWarrior