Prestazioni di array e liste


194

Supponiamo che tu abbia bisogno di un elenco / array di numeri interi di cui hai bisogno di ripetere spesso e intendo estremamente spesso. Le ragioni possono variare, ma dicono che si trova nel cuore del ciclo più interno di un'elaborazione ad alto volume.

In generale, si potrebbe optare per l'utilizzo di elenchi (elenco) a causa della loro flessibilità nelle dimensioni. Inoltre, la documentazione msdn afferma che gli elenchi usano un array internamente e dovrebbero funzionare altrettanto velocemente (una rapida occhiata con Reflector lo conferma). Tuttavia, ci sono alcune spese generali.

Qualcuno l'ha davvero misurato? iterare 6 M volte attraverso un elenco richiederebbe lo stesso tempo di un array?


3
A parte i problemi di prestazioni, preferisco l'uso di array su liste per le loro dimensioni fisse (nei casi in cui non è necessario modificare il numero di elementi, ovviamente). Quando leggo il codice esistente, trovo utile sapere rapidamente che un articolo è costretto ad avere dimensioni fisse, piuttosto che dover ispezionare il codice più in basso nella funzione.
Warren Stevens,

2
T[]vs. List<T>può fare una grande differenza nelle prestazioni. Ho appena ottimizzato un'applicazione a ciclo estremamente intensivo (nidificato) per passare dagli elenchi agli array su .NET 4.0. Mi aspettavo forse un miglioramento dal 5% al ​​10%, ma ho ottenuto una velocità superiore al 40%! Nessun altro cambiamento che passare direttamente dall'elenco all'array. Tutte le enumerazioni sono state fatte con foreachdichiarazioni. In base alla risposta di Marc Gravell, sembra che foreachcon List<T>è particolarmente grave.
Salsa speciale il

Risposte:


221

Molto facile da misurare ...

In un piccolo numero di codice di elaborazione a circuito chiuso in cui so che la lunghezza è fissa, utilizzo array per quel pezzettino di micro-ottimizzazione; le matrici possono essere leggermente più veloci se si utilizza l'indicizzatore / per modulo - ma IIRC ritiene che dipenda dal tipo di dati nell'array. Ma a meno che non sia necessario micro-ottimizzare, mantenerlo semplice e utilizzare List<T>ecc.

Naturalmente, questo vale solo se stai leggendo tutti i dati; un dizionario sarebbe più veloce per le ricerche basate su chiavi.

Ecco i miei risultati usando "int" (il secondo numero è un checksum per verificare che tutti abbiano fatto lo stesso lavoro):

(modificato per correggere bug)

List/for: 1971ms (589725196)
Array/for: 1864ms (589725196)
List/foreach: 3054ms (589725196)
Array/foreach: 1860ms (589725196)

basato sul banco prova:

using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(12345);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next(5000));
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

8
Dettaglio interessante: ecco i tempi RELEASE / DEBUG sul mio rig (.net 3.5 sp1): 0.92, 0.80, 0.96, 0.93; il che mi dice che c'è un po 'di intelligenza nella VM per ottimizzare l'array / for loop di circa il 10% in più rispetto agli altri casi.
David Schmitt,

2
Sì, esiste l'ottimizzazione JIT per array / for. In realtà, avevo l'impressione che includesse il caso Length (poiché sa che è stato risolto), quindi perché non l'ho estratto prima (a differenza di List in cui l'ho fatto). Grazie per l'aggiornamento.
Marc Gravell

2
Strano. I miei test molto simili non mostrano alcuna differenza significativa tra for e foreach quando si usano array. Investigherà a fondo in un post sul blog con un benchmark che altre persone possono eseguire e mi invierà risultati per ...
Jon Skeet,

1
Ottengo risultati notevolmente diversi se utilizzo una variabile diversa per chk per ogni test (chk1, chk2, chk3, chk4). Elenco / per = 1303ms, matrice / per = 433ms. Qualche idea sul perché?
Jon

4
Il link menzionato nel commento sopra di Jon al blog di Jon Skeet è stato interrotto. Di seguito è riportato il link aggiornato. codeblog.jonskeet.uk/2009/01/29/…
Josh DeLong

88

Sommario:

  • La matrice deve usare:

    • Il più spesso possibile. È veloce e richiede il più piccolo intervallo di RAM per la stessa quantità di informazioni.
    • Se conosci il conteggio esatto delle celle necessarie
    • Se i dati sono stati salvati nell'array <85000 b (85000/32 = 2656 elementi per dati interi)
    • Se necessario alta velocità di accesso casuale
  • L'elenco deve usare:

    • Se necessario per aggiungere celle alla fine dell'elenco (spesso)
    • Se necessario per aggiungere celle all'inizio / al centro dell'elenco (NON SPESSO)
    • Se i dati sono stati salvati nell'array <85000 b (85000/32 = 2656 elementi per dati interi)
    • Se necessario alta velocità di accesso casuale
  • LinkedList deve usare:

    • Se necessario per aggiungere celle all'inizio / al centro / alla fine dell'elenco (spesso)

    • Se necessario solo accesso sequenziale (avanti / indietro)

    • Se devi salvare oggetti GRANDI, ma il numero di oggetti è basso.

    • Meglio non usare per una grande quantità di elementi, in quanto utilizza memoria aggiuntiva per i collegamenti.

      Se non sei sicuro di aver bisogno di LinkedList, NON LO AVETE BISOGNO.


Più dettagli:

significato del colore

Matrice vs Elenco vs Elenco collegato

Molto più dettagli:

https://stackoverflow.com/a/29263914/4423545


Sono leggermente confuso dalla tua affermazione che l'elenco anteposto è relativamente veloce ma l'inserimento è lento. Anche l'inserimento è un tempo lineare e più veloce del 50% in media rispetto al prepend.
Mike Marynowski,

1
@MikeMarynowski nell'elenco c # è un wrapper per Array. Quindi in caso di inserimento nell'elenco avrai un tempo lineare solo fino a un certo punto. Dopo questo sistema creerà un nuovo array più grande e avrà bisogno di tempo per copiare gli elementi da quello vecchio.
Andrew,

Stessa cosa con antepone.
Mike Marynowski,

Un'operazione di prepend è solo un inserimento a 0. È l'inserzione peggiore in termini di prestazioni, quindi se l'inserimento è lento, il prepend è ancora più lento.
Mike Marynowski,

Sia insert che prepend sono O (n) (ammortizzati). Un anteprime È un inserto, ma è l'inserto più lento possibile perché deve spostare TUTTI gli elementi dell'elenco di un punto verso l'alto. Un inserimento in una posizione casuale deve solo spostare in alto gli articoli che si trovano in un indice più alto rispetto al punto di inserimento, quindi in media il 50% degli articoli.
Mike Marynowski,

26

Penso che la performance sarà abbastanza simile. Il sovraccarico che è coinvolto quando si utilizza un Elenco contro un array è, IMHO quando si aggiungono elementi all'elenco e quando l'elenco deve aumentare le dimensioni dell'array che sta utilizzando internamente, quando viene raggiunta la capacità dell'array.

Supponiamo di avere un Elenco con una capacità di 10, quindi l'Elenco aumenterà la sua capacità una volta che si desidera aggiungere l'undicesimo elemento. È possibile ridurre l'impatto sulle prestazioni inizializzando la capacità dell'elenco sul numero di elementi che verranno conservati.

Ma, per capire se l'iterazione su un elenco è rapida quanto l'iterazione su un array, perché non lo confronti?

int numberOfElements = 6000000;

List<int> theList = new List<int> (numberOfElements);
int[] theArray = new int[numberOfElements];

for( int i = 0; i < numberOfElements; i++ )
{
    theList.Add (i);
    theArray[i] = i;
}

Stopwatch chrono = new Stopwatch ();

chrono.Start ();

int j;

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theList[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the List took {0} msec", chrono.ElapsedMilliseconds));

 chrono.Reset();

 chrono.Start();

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theArray[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the array took {0} msec", chrono.ElapsedMilliseconds));

 Console.ReadLine();

Sul mio sistema; l'iterazione sull'array ha richiesto 33msec; l'iterazione dell'elenco ha richiesto 66msec.

Ad essere sincero, non mi aspettavo che la variazione sarebbe stata così grande. Quindi, ho messo la mia iterazione in un ciclo: ora, eseguo entrambe le iterazioni 1000 volte. I risultati sono:

l'iterazione dell'elenco ha richiesto 67146 msec l'iterazione dell'array ha richiesto 40821 msec

Ora, la variazione non è più così grande, ma comunque ...

Pertanto, ho avviato .NET Reflector e il getter dell'indicizzatore della classe List è simile al seguente:

public T get_Item(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    return this._items[index];
}

Come puoi vedere, quando usi l'indicizzatore dell'Elenco, l'Elenco esegue un controllo se non esci dai limiti dell'array interno. Questo controllo aggiuntivo ha un costo.


Ciao Frederik, grazie! Come spiegheresti che la tua lista ha impiegato il doppio del tempo delle matrici. Non quello che ti aspetteresti. Hai provato ad aumentare il numero di elementi?
Boaz,

1
Non restituire this._items [indice]; hai già lanciato un'eccezione se l'indice era fuori portata? Perché .NET ha questo controllo extra quando il risultato finale è lo stesso con o senza?
John Mercier,

@ John Mercier il segno di spunta è confrontato con la dimensione dell'elenco (numero di elementi attualmente contenuti), che è diverso e probabilmente inferiore alla capacità dell'array _items. L'array è allocato con una capacità in eccesso per rendere più rapida l'aggiunta di elementi futuri, non richiedendo la riassegnazione per ogni aggiunta.
Trasvi,

21

se stai ottenendo un solo valore da uno dei due (non in un ciclo), entrambi eseguono il controllo dei limiti (sei nel codice gestito, ricorda) è solo l'elenco che lo fa due volte. Vedi le note in seguito per capire perché probabilmente non è un grosso problema.

Se stai usando il tuo per (int int i = 0; i <x. [Lunghezza / Conteggio]; i ++) la differenza chiave è la seguente:

  • Vettore:
    • il controllo dei limiti viene rimosso
  • elenchi
    • viene eseguito il controllo dei limiti

Se stai usando foreach, la differenza chiave è la seguente:

  • Vettore:
    • nessun oggetto è assegnato per gestire l'iterazione
    • il controllo dei limiti viene rimosso
  • Elenco tramite una variabile nota come Elenco.
    • la variabile di gestione dell'iterazione è allocata in pila
    • viene eseguito il controllo dei limiti
  • Elenco tramite una variabile nota come IList.
    • la variabile di gestione dell'iterazione è allocata in heap
    • viene eseguito anche il controllo dei limiti, mentre i valori degli elenchi non possono essere modificati durante il foreach mentre l'array può esserlo.

Il controllo dei limiti spesso non è un grosso problema (specialmente se si è su una CPU con una pipeline profonda e la previsione del ramo - la norma per la maggior parte dei giorni), ma solo la tua profilazione può dirti se questo è un problema. Se ci si trova in parti del codice in cui si stanno evitando allocazioni di heap (buoni esempi sono le librerie o le implementazioni di hashcode), assicurarsi che la variabile sia digitata come Elenco non IList eviterà tale insuccesso. Come sempre profilo se è importante.


11

[ Vedi anche questa domanda ]

Ho modificato la risposta di Marc per utilizzare numeri casuali effettivi e fare lo stesso lavoro in tutti i casi.

risultati:

         per foreach
Matrice: 1575 ms 1575 ms (+ 0%)
Elenco: 1630 ms 2627 ms (+ 61%)
         (+ 3%) (+ 67%)

(Checksum: -1000038876)

Compilato come versione sotto VS 2008 SP1. Esecuzione senza debug su Q6600@2.40GHz, .NET 3.5 SP1.

Codice:

class Program
{
    static void Main(string[] args)
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(1);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next());
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = arr.Length;
            for (int i = 0; i < len; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
        Console.WriteLine();

        Console.ReadLine();
    }
}

È strano - ho appena eseguito il tuo codice esatto, creato dalla riga di comando (3.5SP1) con / o + / debug- e i miei risultati sono: list / for: 1524; array / per: 1472; Lista / foreach: 4128; matrice / foreach: 1484.
Jon Skeet,

Dici che questo è stato compilato come release - posso solo verificare che tu l'abbia eseguito invece di debug? Domanda sciocca, lo so, ma non posso spiegare i risultati altrimenti ...
Jon Skeet,

2

Le misure sono buone, ma otterrai risultati significativamente diversi a seconda di cosa stai facendo esattamente nel tuo ciclo interno. Misura la tua situazione. Se stai usando il multi-threading, quella da sola è un'attività non banale.


2

In effetti, se si eseguono calcoli complessi all'interno del ciclo, le prestazioni dell'indicizzatore di array rispetto all'indicizzatore di elenco potrebbero essere così marginalmente piccole che alla fine non importa.


2

Eccone uno che utilizza Dictionaries, IEnumerable:

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

static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);

        for (int i = 0; i < 6000000; i++)
        {
                list.Add(i);
        }
        Console.WriteLine("Count: {0}", list.Count);

        int[] arr = list.ToArray();
        IEnumerable<int> Ienumerable = list.ToArray();
        Dictionary<int, bool> dict = list.ToDictionary(x => x, y => true);

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }

        Console.WriteLine("Ienumerable/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }

        Console.WriteLine("Dict/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);


        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }

        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);



        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Ienumerable/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Dict/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

2

Non tentare di aggiungere capacità aumentando il numero di elementi.

Prestazione

List For Add: 1ms
Array For Add: 2397ms

    Stopwatch watch;
        #region --> List For Add <--

        List<int> intList = new List<int>();
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            intList.Add(rand.Next());
        }
        watch.Stop();
        Console.WriteLine("List For Add: {0}ms", watch.ElapsedMilliseconds);
        #endregion

        #region --> Array For Add <--

        int[] intArray = new int[0];
        watch = Stopwatch.StartNew();
        int sira = 0;
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            sira += 1;
            Array.Resize(ref intArray, intArray.Length + 1);
            intArray[rpt] = rand.Next();

        }
        watch.Stop();
        Console.WriteLine("Array For Add: {0}ms", watch.ElapsedMilliseconds);

        #endregion

Ottengo il ridimensionamento di un array 60k volte sarà lento ... Sicuramente nell'uso del mondo reale, l'approccio sarebbe solo quello di verificare quanti slot extra hai bisogno, ridimensionarlo a una lunghezza di + 60k, quindi comprimere gli inserti.
tobriand,

Il ridimensionamento di un array è molto veloce se si raddoppia la dimensione ogni volta che si trova bisogno di più spazio. Ho scoperto che sembra impiegare più o meno lo stesso tempo in quanto lo fa semplicemente ridimensionandolo una volta dopo la dichiarazione iniziale. Ciò ti dà la flessibilità di un elenco e la maggior parte della velocità di un array.
user1318499

2

Ero preoccupato che i benchmark pubblicati in altre risposte avrebbero lasciato spazio al compilatore per ottimizzare, eliminare o unire i loop, quindi ne ho scritto uno che:

  • Ingressi imprevedibili usati (casuali)
  • Esegue un calcolato con il risultato stampato sulla console
  • Modifica i dati di input ogni ripetizione

Il risultato è che un array diretto ha prestazioni migliori di circa il 250% rispetto a un accesso a un array racchiuso in un IList:

  • 1 miliardo di accessi all'array: 4000 ms
  • 1 miliardo di accessi all'elenco: 10000 ms
  • 100 milioni di accessi all'array: 350 ms
  • 100 milioni di accessi all'elenco: 1000 ms

Ecco il codice:

static void Main(string[] args) {
  const int TestPointCount = 1000000;
  const int RepetitionCount = 1000;

  Stopwatch arrayTimer = new Stopwatch();
  Stopwatch listTimer = new Stopwatch();

  Point2[] points = new Point2[TestPointCount];
  var random = new Random();
  for (int index = 0; index < TestPointCount; ++index) {
    points[index].X = random.NextDouble();
    points[index].Y = random.NextDouble();
  }

  for (int repetition = 0; repetition <= RepetitionCount; ++repetition) {
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Start();
    }
    doWorkOnArray(points);
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Stop();
    }

    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Start();
    }
    doWorkOnList(points);
    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Stop();
    }
  }

  Console.WriteLine("Ignore this: " + points[0].X + points[0].Y);
  Console.WriteLine(
    string.Format(
      "{0} accesses on array took {1} ms",
      RepetitionCount * TestPointCount, arrayTimer.ElapsedMilliseconds
    )
  );
  Console.WriteLine(
    string.Format(
      "{0} accesses on list took {1} ms",
      RepetitionCount * TestPointCount, listTimer.ElapsedMilliseconds
    )
  );

}

private static void doWorkOnArray(Point2[] points) {
  var random = new Random();

  int pointCount = points.Length;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

private static void doWorkOnList(IList<Point2> points) {
  var random = new Random();

  int pointCount = points.Count;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

0

Poiché List <> utilizza gli array internamente, le prestazioni di base dovrebbero essere le stesse. Due motivi, perché l'elenco potrebbe essere leggermente più lento:

  • Per cercare un elemento nell'elenco, viene chiamato un metodo List, che esegue la ricerca nell'array sottostante. Quindi è necessario un ulteriore metodo di chiamata lì. D'altra parte il compilatore potrebbe riconoscerlo e ottimizzare la chiamata "non necessaria".
  • Il compilatore potrebbe eseguire alcune ottimizzazioni speciali se conosce le dimensioni dell'array, cosa che non può fare per un elenco di lunghezza sconosciuta. Ciò potrebbe comportare un miglioramento delle prestazioni se hai solo alcuni elementi nell'elenco.

Per verificare se fa alcuna differenza per te, probabilmente è meglio regolare le funzioni di temporizzazione pubblicate su un elenco delle dimensioni che intendi utilizzare e vedere come sono i risultati per il tuo caso speciale.


0

Da quando ho avuto una domanda simile, questo mi ha fatto iniziare rapidamente.

La mia domanda è un po 'più specifica: "qual è il metodo più veloce per un'implementazione di array riflessivo"

I test eseguiti da Marc Gravell mostrano molto, ma non esattamente i tempi di accesso. Il suo tempismo include anche il looping sull'array e sugli elenchi. Da quando ho anche escogitato un terzo metodo che volevo testare, un 'Dizionario', solo per confrontare, ho esteso il codice del test hist.

Primo, faccio un test usando una costante, che mi dà un certo tempismo incluso il loop. Questo è un tempismo "nudo", escluso l'accesso effettivo. Quindi eseguo un test con l'accesso alla struttura del soggetto, questo mi dà il tempismo, il looping e l'accesso effettivo "overhead incluso".

La differenza tra i tempi "nudi" e i tempi "non utilizzati" mi dà un'indicazione dei tempi di "accesso alla struttura".

Ma quanto è preciso questo tempismo? Durante il test le finestre impiegheranno un po 'di tempo a tagliare per shure. Non ho informazioni sull'intervallo di tempo, ma suppongo che sia distribuito uniformemente durante il test e nell'ordine di decine di msec, il che significa che l'accuratezza dei tempi dovrebbe essere dell'ordine di +/- 100 msec o giù di lì. Una stima un po 'approssimativa? Comunque una fonte di un errore sistematico di mureure.

Inoltre, i test sono stati eseguiti in modalità "Debug" senza ottimizzazione. In caso contrario, il compilatore potrebbe modificare il codice di test effettivo.

Quindi, ottengo due risultati, uno per una costante, contrassegnato '(c)' e uno per l'accesso contrassegnato '(n)' e la differenza 'dt' mi dice quanto tempo impiega l'accesso effettivo.

E questi sono i risultati:

          Dictionary(c)/for: 1205ms (600000000)
          Dictionary(n)/for: 8046ms (589725196)
 dt = 6841

                List(c)/for: 1186ms (1189725196)
                List(n)/for: 2475ms (1779450392)
 dt = 1289

               Array(c)/for: 1019ms (600000000)
               Array(n)/for: 1266ms (589725196)
 dt = 247

 Dictionary[key](c)/foreach: 2738ms (600000000)
 Dictionary[key](n)/foreach: 10017ms (589725196)
 dt = 7279

            List(c)/foreach: 2480ms (600000000)
            List(n)/foreach: 2658ms (589725196)
 dt = 178

           Array(c)/foreach: 1300ms (600000000)
           Array(n)/foreach: 1592ms (589725196)
 dt = 292


 dt +/-.1 sec   for    foreach
 Dictionary     6.8       7.3
 List           1.3       0.2
 Array          0.2       0.3

 Same test, different system:
 dt +/- .1 sec  for    foreach
 Dictionary     14.4   12.0
       List      1.7    0.1
      Array      0.5    0.7

Con stime migliori sugli errori di temporizzazione (come rimuovere l'errore di misurazione sistematico dovuto al time-slicing?) Si potrebbe dire di più sui risultati.

Sembra che List / foreach abbia l'accesso più veloce ma l'overhead lo sta uccidendo.

La differenza tra List / for e List / foreach è sconosciuta. Forse è coinvolto un po 'di incassi?

Inoltre, per l'accesso a un array non importa se si utilizza un forloop o un foreachloop. I risultati di temporizzazione e la sua precisione rendono i risultati "comparabili".

L'uso di un dizionario è di gran lunga il più lento, l'ho considerato solo perché sul lato sinistro (l'indicizzatore) ho un elenco sparso di numeri interi e non un intervallo come viene utilizzato in questi test.

Ecco il codice di prova modificato.

Dictionary<int, int> dict = new Dictionary<int, int>(6000000);
List<int> list = new List<int>(6000000);
Random rand = new Random(12345);
for (int i = 0; i < 6000000; i++)
{
    int n = rand.Next(5000);
    dict.Add(i, n);
    list.Add(n);
}
int[] arr = list.ToArray();

int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // dict[i];
    }
}
watch.Stop();
long c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += dict[i];
    }
}
watch.Stop();
long n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // list[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(c)/for: {0}ms ({1})", c_dt, chk);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += list[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += 1; // arr[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("              Array(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += arr[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += 1; // dict[i]; ;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += dict[i]; ;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("          Array(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

0

In alcuni brevi test ho trovato una combinazione dei due migliore in quella che definirei matematica ragionevolmente intensiva:

Genere: List<double[]>

Tempo: 00: 00: 05.1861300

Genere: List<List<double>>

Tempo: 00: 00: 05.7941351

Genere: double[rows * columns]

Tempo: 00: 00: 06.0547118

Esecuzione del codice:

int rows = 10000;
int columns = 10000;

IMatrix Matrix = new IMatrix(rows, columns);

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();


for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] = Math.E;

for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] *= -Math.Log(Math.E);


stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;

Console.WriteLine(ts.ToString());

Vorrei che avessimo fatto alcune classi di matrice accelerata hardware di prim'ordine come il team .NET ha fatto con la System.Numerics.Vectorsclasse!

C # potrebbe essere il miglior linguaggio ML con un po 'più di lavoro in quest'area!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.