Question

Le code ci-dessous vérifie les performances de trois manières différentes de réaliser la même solution.

    public static void Main(string[] args)
    {
        // for loop
        {
            Stopwatch sw = Stopwatch.StartNew();

            int accumulator = 0;
            for (int i = 1; i <= 100000000; ++i)
            {
                accumulator += i;
            }

            sw.Stop();

            Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, accumulator);
        }

        //Enumerable.Range
        {
            Stopwatch sw = Stopwatch.StartNew();

            var ret = Enumerable.Range(1, 100000000).Aggregate(0, (accumulator, n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
        }

        //self-made IEnumerable<int>
        {
            Stopwatch sw = Stopwatch.StartNew();

            var ret = GetIntRange(1, 100000000).Aggregate(0, (accumulator, n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
        }
    }

    private static IEnumerable<int> GetIntRange(int start, int count)
    {
        int end = start + count;

        for (int i = start; i < end; ++i)
        {
            yield return i;
        }
    }
}

Les résultats sont:

time = 306; result = 987459712
time = 1301; result = 987459712
time = 2860; result = 987459712

Il n’est pas surprenant que la boucle "for for" est plus rapide que les deux autres solutions, car Enumerable.Aggregate prend plus d'appels de méthode. Cependant, cela me surprend vraiment que " Enumerable.Range " est plus rapide que le "IEnumerable self-made". Je pensais que Enumerable.Range aurait plus de temps système que la simple méthode GetIntRange.

Quelles en sont les raisons possibles?

Était-ce utile?

La solution

Pourquoi Enumerable.Range devrait-il être plus lent que votre GetIntRange que vous avez créé vous-même? En fait, si Enumerable.Range était défini comme

public static class Enumerable {
    public static IEnumerable<int> Range(int start, int count) {
        var end = start + count;
        for(var current = start; current < end; ++current) {
            yield return current;
        }
    }
}

alors il devrait être exactement aussi rapide que votre propre GetIntRange . C’est en fait l’implémentation de référence pour Enumerable.Range , en l’absence de ruse de la part du compilateur ou du programmeur.

Vous souhaiterez peut-être comparer votre GetIntRange et votre System.Linq.Enumerable.Range avec l'implémentation suivante (bien sûr, compilez en mode édition, comme le souligne Rob) . Cette implémentation peut être légèrement optimisée par rapport à ce qu'un compilateur générerait à partir d'un bloc itérateur.

public static class Enumerable {
    public static IEnumerable<int> Range(int start, int count) {
        return new RangeEnumerable(start, count);
    }
    private class RangeEnumerable : IEnumerable<int> {
        private int _Start;
        private int _Count;
        public RangeEnumerable(int start, int count) {
            _Start = start;
            _Count = count;
        }
        public virtual IEnumerator<int> GetEnumerator() {
            return new RangeEnumerator(_Start, _Count);
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }
    private class RangeEnumerator : IEnumerator<int> {
        private int _Current;
        private int _End;
        public RangeEnumerator(int start, int count) {
            _Current = start - 1;
            _End = start + count;
        }
        public virtual void Dispose() {
            _Current = _End;
        }
        public virtual void Reset() {
            throw new NotImplementedException();
        }
        public virtual bool MoveNext() {
            ++_Current;
            return _Current < _End;
        }
        public virtual int Current { get { return _Current; } }
        object IEnumerator.Current { get { return Current; } }
    }
}

Autres conseils

Je suppose que vous exécutez un débogueur. Voici mes résultats, construits à partir de la ligne de commande avec "/ o + / debug-"

time = 142; result = 987459712
time = 1590; result = 987459712
time = 1792; result = 987459712

Il y a encore une légère différence, mais elle n’est pas aussi prononcée. Les implémentations de bloc Iterator ne sont pas aussi efficaces qu'une solution sur mesure, mais elles sont plutôt efficaces.

En supposant qu'il s'agisse d'une version validée en cours d'exécution, sinon toutes les comparaisons sont fausses car le JIT ne fonctionnera pas à fond.

Vous pouvez regarder l'assemblage avec réflecteur et voir ce que vous obtenez 'déclaration est également élargie. Le compilateur va créer une classe pour encapsuler l'itérateur. Le code généré contient peut-être plus d’entretien courant que l’implémentation de Enumerable.Range, qui est probablement codée à la main

Une légère différence dans la sortie du réflecteur (ainsi que le contrôle des arguments et le niveau supplémentaire d'internalisation ne sont absolument pas pertinents ici). Le code essentiel ressemble plus à:

public static IEnumerable<int> Range(int start, int count) {
    for(int current = 0; current < count; ++current) {
        yield return start + current;
    }
}

C'est-à-dire qu'au lieu d'utiliser une autre variable locale, ils ajoutent un supplément pour chaque rendement.

J’ai essayé d’évaluer cela, mais je ne peux pas arrêter suffisamment de processus externes pour obtenir des résultats compréhensibles. J'ai également essayé deux fois chaque test pour ignorer les effets du compilateur JIT, mais même cela a des résultats "intéressants".

Voici un exemple de mes résultats:

Run 0:
time = 4149; result = 405000000450000000
time = 25645; result = 405000000450000000
time = 39229; result = 405000000450000000
time = 29872; result = 405000000450000000

time = 4277; result = 405000000450000000
time = 26878; result = 405000000450000000
time = 26333; result = 405000000450000000
time = 26684; result = 405000000450000000

Run 1:
time = 4063; result = 405000000450000000
time = 22714; result = 405000000450000000
time = 34744; result = 405000000450000000
time = 26954; result = 405000000450000000

time = 4033; result = 405000000450000000
time = 26657; result = 405000000450000000
time = 25855; result = 405000000450000000
time = 25031; result = 405000000450000000

Run 2:
time = 4021; result = 405000000450000000
time = 21815; result = 405000000450000000
time = 34304; result = 405000000450000000
time = 32040; result = 405000000450000000

time = 3993; result = 405000000450000000
time = 24779; result = 405000000450000000
time = 29275; result = 405000000450000000
time = 32254; result = 405000000450000000

et le code

using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;

namespace RangeTests
{
  class TestRange
  {
    public static void Main(string[] args)
    {
      for(int l = 1; l <= 2; ++l)
      {
        const int N = 900000000;
        System.GC.Collect(2);
        // for loop
        {
            Stopwatch sw = Stopwatch.StartNew();

            long accumulator = 0;
            for (int i = 1; i <= N; ++i)
            {
                accumulator += i;
            }

            sw.Stop();

            Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, accumulator);
        }
        System.GC.Collect(2);

        //Enumerable.Range
        {
            Stopwatch sw = Stopwatch.StartNew();

            var ret = Enumerable.Range(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
        }
        System.GC.Collect(2);

        //self-made IEnumerable<int>
        {
            Stopwatch sw = Stopwatch.StartNew();

            var ret = GetIntRange(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
        }
        System.GC.Collect(2);

        //self-made adjusted IEnumerable<int>
        {
            Stopwatch sw = Stopwatch.StartNew();

            var ret = GetRange(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
        }
        System.GC.Collect(2);
        Console.WriteLine();
    } }

    private static IEnumerable<int> GetIntRange(int start, int count)
    {
        int end = start + count;

        for (int i = start; i < end; ++i)
        {
            yield return i;
        }
    }

    private static IEnumerable<int> GetRange(int start, int count)
    {
        for (int i = 0; i < count; ++i)
        {
            yield return start + i;
        }
    }
} }

compilé avec

csc.exe -optimize+ -debug- RangeTests.cs
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top