. Velocità netta vs velocità codice personalizzato

2

In un int?[] che si è procurato di avere valori originali, volevo trovare l'indice di null, che sarà solo un indice singolo.

Ho eseguito il drill su Array.IndexOf(T[] array, T value) .NET e, dopo tutti i contratti e i controlli dell'indice, si riduce a questo metodo:

internal virtual int IndexOf(T[] array, T value, int startIndex, int count){
    int endIndex = startIndex + count;
    for (int i = startIndex; i < endIndex; i++)
        if (Equals(array[i], value)) return i;

    return -1;
}

Ho tre diverse istanze che tentano un ciclo di base con array[i] == null , Equals(array[i], null) e usando Array.IndexOf(null) . Mi rendo conto che le zecche e il tempo di cronometro sono relativi a ciò che accade sulla macchina in quel momento e alla macchina in generale. Ma questo è il codice e i benchmark:

class Program
{
    static void Main(string[] args)
    {
        int?[] jankedArray;
        int missingElement = GenRandomizedArrayWithExtraEmptyElement(10000, out jankedArray);

        var sw = new Stopwatch();
        List<long> DotNetTicks = new List<long>();
        List<long> LikeDotNetTicks = new List<long>();
        List<long> BasicLoopTicks = new List<long>();
        IArrayAnalyzer arrayAnalyzer;
        for (var i = 0; i < 100000; i++)
        {
            arrayAnalyzer = new AnalyzerLikeDotNet();
            sw.Restart(); arrayAnalyzer.GetMissingElement(jankedArray); sw.Stop();
            LikeDotNetTicks.Add(sw.ElapsedTicks);

            arrayAnalyzer = new AnalyzerBasic();
            sw.Restart(); arrayAnalyzer.GetMissingElement(jankedArray); sw.Stop();
            BasicLoopTicks.Add(sw.ElapsedTicks);

            arrayAnalyzer = new AnalyzerUsingDotNet();
            sw.Restart(); arrayAnalyzer.GetMissingElement(jankedArray); sw.Stop();
            DotNetTicks.Add(sw.ElapsedTicks);
        }

        Console.WriteLine("LikeDotNet / DotNet = " + LikeDotNetTicks.Average() / DotNetTicks.Average());
        Console.WriteLine("Basic / DotNet = " + BasicLoopTicks.Average() / DotNetTicks.Average());

        Console.WriteLine("");
        Console.WriteLine("Press the Any key to continue");
        Console.ReadKey();
    }

    public static int GenRandomizedArrayWithExtraEmptyElement(int valueCount, out int?[] incompleteArray)
    {
        incompleteArray = new int?[valueCount + 1];
        Random random = new Random();

        int randomMissingIndex = random.Next(0, valueCount);

        var valueArray = new List<int>();
        for (var i = 1; i <= valueCount; i++) valueArray.Add(i);

        var arrayElementAt = 0;
        while (valueArray.Count > 0)
        {
            if (arrayElementAt != randomMissingIndex)
            {
                var randomElement = random.Next(0, valueArray.Count);
                var valueAtRandom = valueArray.ElementAt(randomElement);
                valueArray.RemoveAt(randomElement);
                incompleteArray[arrayElementAt] = valueAtRandom;
            }
            arrayElementAt++;
        }

        return randomMissingIndex;
    }
}

public interface IArrayAnalyzer
{
    int GetMissingElement(int?[] incompleteArray);
}

public class AnalyzerUsingDotNet : IArrayAnalyzer
{
    public int GetMissingElement(int?[] incompleteArray)
    {
        return Array.IndexOf(incompleteArray, null);
    }
}

public class AnalyzerLikeDotNet : IArrayAnalyzer
{
    public int GetMissingElement(int?[] array)
    {
        for (int i = 0; i < array.Length; i++)
            if (Equals(array[i], null)) return i;

        return -1;
    }
}

public class AnalyzerBasic : IArrayAnalyzer
{
    public int GetMissingElement(int?[] array)
    {
        for (int i = 0; i < array.Length; i++)
            if (array[i] == null) return i;

        return -1;
    }
}
  • Output:
  • LikeDotNet / DotNet = 81.3577324867023
  • Base / DotNet = 3.29459064916075

Cosa mi manca tra AnalyzerLikeDotNet e AnalyzerUsingDotNet che rende così diverso il tempo di esecuzione?

    
posta Suamere 07.08.2016 - 01:02
fonte

2 risposte

3

Sulla mia macchina, il risultato del benchmark (compilato in modalità Release ottimizzata) è

 LikeDotNet / DotNet = 101.911379048464

 Basic / DotNet = 0.979227574248443

quindi il secondo è prossimo al rapporto di 1, che è una strong indicazione che l'implementazione interna nel framework è molto simile al tuo caso Basic . La differenza del 2% è probabilmente dovuta al sovraccarico della chiamata al metodo addizionale o ai controlli aggiuntivi all'interno del framework.

Dai un'occhiata al link al codice sorgente che hai postato sembra Array.IndexOf delega le sue chiamate a

  EqualityComparer<T>.Default.IndexOf

(riga 1406) e il confronto Default è inizializzato da un CreateComparer metodo che seleziona una delle numerose derivazioni speciali EqualityComparer<T> , ottimizzate per alcuni tipi standard. Per int? questo significa che verrà utilizzato il metodo IndexOf di NullableEqualityComparer<T> , che utilizza .HasValue per null testing, non Equals . Un rapido punto di riferimento con

    public int GetMissingElement(int?[] array)
    {
        for (int i = 0; i < array.Length; i++)
            if (!array[i].HasValue) return i;
        return -1;
    }

mostra prestazioni simili come il tuo Basic test utilizzando == (o simile a chiamare Array.IndexOf direttamente). Quindi penso che questo sia ciò che sta realmente accadendo qui - .HasValue ha probabilmente prestazioni molto migliori (> factor 100) rispetto a Equals per int? .

    
risposta data 08.02.2017 - 10:47
fonte
3

Value types do not provide an overload for == by default. However, most of the value types provided by the framework provide their own overload. The default implementation of Equals for a value type is provided by ValueType, and uses reflection to make the comparison, which makes it significantly slower than a type-specific implementation normally would be. This implementation also calls Equals on pairs of references within the two values being compared.

collegamento

    
risposta data 08.02.2017 - 03:15
fonte

Leggi altre domande sui tag