Pergunta

O código a seguir é verificar o desempenho de três maneiras diferentes de fazer mesma solução.

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

Os resultados são:

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

Não é de surpreender que o "loop" é mais rápido do que as outras duas soluções, porque Enumerable.Aggregate leva mais chamadas de método. No entanto, ele realmente me surpreende que "Enumerable.Range" é mais rápido do que o "IEnumerable self-made". Pensei que Enumerable.Range teria mais sobrecarga do que o método GetIntRange simples.

Quais são as possíveis razões para isso?

Foi útil?

Solução

Por que Enumerable.Range ser qualquer mais lento do que o seu GetIntRange self-made? Na verdade, se Enumerable.Range foram definidos 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;
        }
    }
}

então ele deve ser exatamente tão rápido quanto seu GetIntRange self-made. Esta é, de facto, a implementação de referência para Enumerable.Range, na ausência de quaisquer truques por parte do compilador ou programador.

Você pode querer comparar o seu GetIntRange e System.Linq.Enumerable.Range com a seguinte implementação (claro, compilação no modo de versão, como Rob aponta). Esta implementação pode ser ligeiramente otimizado com relação ao que um compilador geraria a partir de um bloco de iterator.

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

Outras dicas

Meu palpite é que você está executando em um depurador. Aqui estão meus resultados, tendo construído a partir da linha de comando com "/ o + / debug -"

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

Ainda há uma pequena diferença, mas não é tão pronunciada. Iterator bloquear implementações não são tão eficientes como uma solução sob medida, mas eles são muito bons.

Assumindo que esta é uma versão de construção em execução, caso contrário, todas as comparações estão fora como o JIT não vai estar trabalhando fora plana.

Você pode olhar para o conjunto com refletor e ver o que o 'rendimento 'declaração está sendo expandido também. O compilador será a criação de uma classe para encapsular o iterador. Talvez haja mais tarefas domésticas acontecendo no código gerado do que a implementação de Enumerable.Range que é provável mão-Codificado

A ligeira diferença na saída do refletor (bem como a verificação de argumento e nível extra de internalização definitivamente não é relevante aqui). O código essencial é mais parecido com:

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

Isto é, em vez de uma outra variável local, eles aplicam uma adição extra para cada rendimento.

Eu tentei referência isso, mas eu não consigo parar de processos externos suficientes para obter resultados compreensíveis. Eu também tentei cada teste duas vezes para ignorar os efeitos do compilador JIT, mas mesmo isso tem resultados interessantes ''.

Aqui está um exemplo de meus 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

eo 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 com

csc.exe -optimize+ -debug- RangeTests.cs
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top