Pregunta

El siguiente código está verificando el rendimiento de tres formas diferentes de hacer la misma solución.

    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;
        }
    }
}

Los resultados son:

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

No es sorprendente que el " para bucle " es más rápido que las otras dos soluciones, porque Enumerable.Aggregate toma más invocaciones de métodos. Sin embargo, realmente me sorprende que " Enumerable.Range " es más rápido que el " hecho propio IEnumerable " ;. Pensé que Enumerable.Range tendría más gastos que el simple método GetIntRange.

¿Cuáles son las posibles razones para esto?

¿Fue útil?

Solución

¿Por qué debería Enumerable.Range ser más lento que su GetIntRange hecho a sí mismo? De hecho, si Enumerable.Range se definiera como

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;
        }
    }
}

entonces debería ser exactamente tan rápido como su GetIntRange hecho a sí mismo. De hecho, esta es la implementación de referencia para Enumerable.Range , sin ningún truco por parte del compilador o programador.

Es posible que desee comparar su GetIntRange y System.Linq.Enumerable.Range con la siguiente implementación (por supuesto, compilar en modo de lanzamiento, como señala Rob) . Esta implementación puede estar ligeramente optimizada con respecto a lo que generaría un compilador a partir de un bloque de iteradores.

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; } }
    }
}

Otros consejos

Mi conjetura es que estás ejecutando en un depurador. Aquí están mis resultados, habiendo creado desde la línea de comandos con " / o + / debug- "

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

Todavía hay una ligera diferencia, pero no es tan pronunciada. Las implementaciones de Iterator Block no son tan eficientes como una solución hecha a la medida, pero son bastante buenas.

Suponiendo que se trata de una versión de compilación en ejecución, de lo contrario, todas las comparaciones están desactivadas, ya que el JIT no funcionará a toda máquina.

Puede mirar el ensamblaje con reflector y ver cuál es el rendimiento 'declaración se está ampliando también. El compilador creará una clase para encapsular el iterador. Tal vez haya más tareas de limpieza en el código generado que en la implementación de Enumerable.Range, que probablemente esté codificado a mano

Una ligera diferencia en la salida del Reflector (así como la verificación de argumentos y el nivel adicional de internalización definitivamente no son relevantes aquí). El código esencial es más como:

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

Es decir, en lugar de otra variable local, aplican una adición adicional para cada rendimiento.

He intentado comparar esto, pero no puedo detener suficientes procesos externos para obtener resultados comprensibles. También intenté cada prueba dos veces para ignorar los efectos del compilador JIT, pero incluso eso tiene resultados 'interesantes'.

Aquí hay una muestra de mis resultados:

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

y el código

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;
        }
    }
} }

compilado con

csc.exe -optimize+ -debug- RangeTests.cs
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top