Performance de l'agrégat par rapport à la somme en LINQ

Trois implémentations différentes de trouver la somme d'une IEnumerable source sont donnés ci-dessous avec le temps pris quand la source a 10.000 entiers.

source.Aggregate(0, (result, element) => result + element);  

prend 3 ms

source.Sum(c => c);

prend 12 ms

source.Sum();

prend 1 ms

je me demande pourquoi la seconde implémentation est quatre fois plus chère que la première. Ça ne devrait pas être la même chose que la troisième implémentation.

24
demandé sur Gopal 2012-06-14 13:18:55

1 réponses

Note: mon ordinateur exécute .Net 4.5 RC, il est donc possible que mes résultats en soient affectés.

mesurer le temps qu'il faut pour exécuter une méthode une seule fois n'est généralement pas très utile. Il peut être facilement dominé par des choses comme la compilation JIT, qui ne sont pas des goulots d'étranglement réels dans le code réel. Pour cette raison, j'ai mesuré l'exécution de chaque méthode 100× (en mode Release sans débogueur attaché). Mes résultats sont les suivants:

  • Aggregate(): 9 ms
  • Sum(lambda): 12 ms
  • Sum(): 6 ms

Le fait que Sum() est le plus rapide n'est pas surprenant: il contient une boucle simple sans invocations de délégué, qui est vraiment rapide. La différence entre Sum(lambda) et Aggregate() n'est pas presque aussi important que ce que vous avez mesuré, mais il est toujours là. Quelle pourrait être la raison pour cela? Regardons le code décomposé pour les deux méthodes:

public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func)
{
    if (source == null)
        throw Error.ArgumentNull("source");
    if (func == null)
        throw Error.ArgumentNull("func");

    TAccumulate local = seed;
    foreach (TSource local2 in source)
        local = func(local, local2);
    return local;
}

public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
    return source.Select<TSource, int>(selector).Sum();
}

Comme vous pouvez le voir, Aggregate() utilise un boucle, mais Sum(lambda)Select(), qui à son tour utilise un itérateur. Et l'utilisation d'un itérateur signifie qu'il y a une certaine surcharge: créer l'objet itérateur et (probablement plus important) une invocation de méthode de plus pour chaque élément.

vérifions qu'en utilisant Select() est en fait la raison en écrivant notre propre Sum(lambda) deux fois, une fois en utilisant Select(), qui devrait se comporter comme Sum(lambda) dans le cadre, et une fois sans l'aide de Select():

public static int SlowSum<T>(this IEnumerable<T> source, Func<T, int> selector)
{
    return source.Select(selector).Sum();
}

public static int FastSum<T>(this IEnumerable<T> source, Func<T, int> selector)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (selector == null)
        throw new ArgumentNullException("selector");

    int num = 0;
    foreach (T item in source)
        num += selector(item);
    return num;
}

Mes mesures confirmer ce que je pensais:

  • SlowSum(lambda): 12 ms
  • FastSum(lambda): 9 ms
69
répondu svick 2012-06-14 11:40:28