2014-08-31 1 views
3

J'ai une application console qui contient deux méthodes:Pourquoi cette méthode d'extension IEnumerable est-elle beaucoup plus lente qu'une autre méthode d'extension (plus simple) (qui ne fait qu'opérer l'entrée)?

public static IEnumerable<TSource> 
      FooA<TSource>(this IEnumerable<IEnumerable<TSource>> source) 
{ 
    return source.Aggregate((x, y) => x.Intersect(y)); 
} 

public static IEnumerable<TSource> 
      FooB<TSource>(this IEnumerable<IEnumerable<TSource>> source) 
{ 
    foreach (TSource element in source.First()) 
    { 
     yield return element; 
    } 
} 

Ce qu'il fait: les deux prennent une séquence de séquences, FooA produire l'intersection ensemble de tous, puis revenir résultat. FooB itérez simplement la première séquence.

Ce que je ne comprends pas: FooB est plus de 10 fois plus lent que FooA, alors que FooB est en fait beaucoup plus simple (il n'y a pas appel à Intersect() méthode).

Voici les résultats:

00:00:00.0071053 (FooA) 
00:00:00.0875303 (FooB) 

FooB peut être beaucoup plus rapide en retournant directement source.First(), de toute façon je décompilé Distinct méthode utilisant ILSpy et trouvé exactement la même boucle de retour de rendement foreach:

private static IEnumerable<TSource> DistinctIterator<TSource> 
    (IEnumerable<TSource> source, IEqualityComparer<TSource> comparer) 
{ 
    Set<TSource> set = new Set<TSource>(comparer); 
    foreach (TSource current in source) 
    { 
     if (set.Add(current)) 
     { 
      yield return current; 
     } 
    } 
    yield break; 
} 

également : dans le code que j'utilise je ne peux pas retourner source.First() (je reçois CS1622). Ce que je montre ici est en fait un code beaucoup plus simple que je me suis déshabillé pour le débogage.

Code est ici que j'utilise pour le test:

List<List<int>> foo = new List<List<int>>(); 
foo.Add(new List<int>(Enumerable.Range(0, 3000*1000))); 

Stopwatch sa = new Stopwatch(); 
sa.Start(); 
List<int> la = FooA(foo).ToList(); 
Console.WriteLine(sa.Elapsed); 


Stopwatch sb = new Stopwatch(); 
sb.Start(); 
List<int> lb = FooB(foo).ToList(); 
Console.WriteLine(sb.Elapsed); 
+0

Ainsi, 'Intersect' n'est jamais appelé. Bon point. Je n'y ai pas pensé. Il n'explique pas qu'il sera toujours beaucoup plus rapide que 'FooB' cependant – tigrou

+0

Voir ma réponse d'où vient la différence. –

Répondre

5

La raison pour laquelle vous mesurez une telle différence est que l'appel globale renvoie simplement votre liste initiale depuis il n'y a pas d'éléments à agréger car votre liste n'a qu'un seul élément.

Si vous changez à

List<List<int>> foo = new List<List<int>>() 
    { 
     new List<int>(Enumerable.Range(0, 3000 * 1000)), 
     new List<int>(Enumerable.Range(0, 3000 * 1000)), 
    }; 

Avec un seul élément comme vous:

A: 00:00:00.0037843 
B: 00:00:00.0514177 

Mais avec deux éléments:

A: 00:00:00.2130628 
B: 00:00:00.0574932 

A est maintenant beaucoup plus lent. La différence dans le premier exemple était due aux allocations de matrice qui ont provoqué beaucoup plus de cycles CPU.

AllocationAmount AllocationKind 
B  1CAE0   Small 
B  21E5C   Small 
B  20020   Large 
B  40020   Large 
B  80020   Large 
B 100020   Large 
B 200020   Large 
B 400020   Large 
B 800020   Large 
B 1000020   Large 
A B71B20   Large 

Ce sont les événements GC AllocationTick ETW qui sont émis par Garbage Collector. En effet, vous avez comparé des pommes à des oranges. Votre appel Aggregate n'a pratiquement rien fait.

+0

Vous avez raison. L'agrégat ne renvoie pas de 'IEnumerable ' mais de 'TSource' directement. – tigrou

0

Utilisez plutôt:

public static IEnumerable<TSource> FooB<TSource>(this IEnumerable<IEnumerable<TSource>> source) { 
    yield return source.First(); 
} 
0

FooA n'appelle pas du tout Intersect. Il n'y a qu'un seul élément dans la séquence. Aggregate juste le renvoie. Il n'y a rien à agréger.

FooB traverse tous les éléments de la première séquence. Cela prend du temps. Cela prend beaucoup plus de temps que de simplement retourner la première séquence comme le fait FooA.

Questions connexes