web-dev-qa-db-fra.com

Agrégation VS Sum Performance à Linq

Trois mises en œuvre différentes de la recherche de la somme d'une source iEnumerable <int> source sont indiquées ci-dessous avec le temps pris lorsque la source comporte 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 deuxième mise en œuvre est quatre fois plus chère que la première. Ne devrait-il pas être la même chose que la troisième mise en œuvre.

31
Gopal

Remarque: mon ordinateur exécute .NET 4.5 RC, il est donc possible que mes résultats soient affectés par cela.

Mesurer le temps nécessaire 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 en code réel. Pour cette raison, j'ai mesuré l'exécution de chaque méthode 100 × (en mode de libération sans débogueur attachée). Mes résultats sont:

  • 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 déléguées, qui est vraiment rapide. La différence entre Sum(lambda) et Aggregate() n'est pas aussi importante que ce que vous avez mesuré, mais c'est toujours là. Quelle pourrait être la raison pour cela? Regardons le code décompilé 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 constater, Aggregate() utilise une boucle mais Sum(lambda) utilise Select(), qui utilise à son tour un itérateur. Et l'utilisation d'un itérateur signifie qu'il y a des frais généraux: créer l'objet itérateur et (probablement plus important encore) une nouvelle invocation de méthode pour chaque élément.

Vérifions que l'utilisation de Select() est en réalité la raison en écrivant notre propre Sum(lambda) deux fois, une fois l'utilisation Select(), qui devrait se comporter de la même manière que Sum(lambda) Dans le cadre, et une fois sans utiliser 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 confirment ce que je pensais:

  • SlowSum(lambda): 12 ms
  • FastSum(lambda): 9 ms
79
svick