Domanda

Il codice seguente sta controllando le prestazioni di tre diversi modi per fare la stessa soluzione.

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

I risultati sono:

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

Non sorprende che il " per loop " è più veloce delle altre due soluzioni, perché Enumerable.Aggregate accetta più invocazioni di metodo. Tuttavia, mi sorprende davvero che "Enumerable.Range". è più veloce di "IEnumerable self-made". Ho pensato che Enumerable.Range avrebbe avuto un sovraccarico maggiore rispetto al semplice metodo GetIntRange.

Quali sono le possibili ragioni di ciò?

È stato utile?

Soluzione

Perché Enumerable.Range dovrebbe essere più lento del tuo GetIntRange ? In effetti, se Enumerable.Range fosse definito come

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

allora dovrebbe essere esattamente veloce come il tuo GetIntRange fatto da te. Questa è in realtà l'implementazione di riferimento per Enumerable.Range , in assenza di trucchi da parte del compilatore o del programmatore.

Potresti voler confrontare GetIntRange e System.Linq.Enumerable.Range con la seguente implementazione (ovviamente, compila in modalità di rilascio, come sottolinea Rob) . Questa implementazione può essere leggermente ottimizzata rispetto a ciò che un compilatore genererebbe da un blocco iteratore.

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

Altri suggerimenti

Suppongo che tu stia eseguendo un debugger. Ecco i miei risultati, avendo creato dalla riga di comando con " / o + / debug- "

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

C'è ancora una leggera differenza, ma non è così pronunciata. Le implementazioni di blocchi di iteratori non sono abbastanza efficienti come una soluzione su misura, ma sono piuttosto buone.

Supponendo che questa sia una build di rilascio in esecuzione, altrimenti tutti i confronti sono disattivati ??poiché JIT non funzionerà in modo definitivo.

Potresti guardare l'assemblea con reflector e vedere quale sia il rendimento 'anche la dichiarazione si sta espandendo. Il compilatore creerà una classe per incapsulare l'iteratore. Forse c'è più pulizia in corso nel codice generato rispetto all'implementazione di Enumerable.Range che è probabilmente codificato a mano

Una leggera differenza nell'output di Reflector (così come il controllo degli argomenti e il livello extra di internalizzazione sicuramente non rilevanti qui). Il codice essenziale è più simile a:

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

Cioè, invece di un'altra variabile locale, applicano un'aggiunta aggiuntiva per ogni rendimento.

Ho provato a confrontarlo, ma non riesco a fermare abbastanza processi esterni per ottenere risultati comprensibili. Ho anche provato due volte ogni test per ignorare gli effetti del compilatore JIT, ma anche quello ha risultati "interessanti".

Ecco un esempio dei miei risultati:

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

e il codice

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

compilato con

csc.exe -optimize+ -debug- RangeTests.cs
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top