Wydajność tablic vs. list


194

Powiedzmy, że musisz mieć listę / tablicę liczb całkowitych, które potrzebujesz iterować często, a mam na myśli bardzo często. Przyczyny mogą być różne, ale powiedzmy, że jest to sedno najbardziej wewnętrznej pętli przetwarzania o dużej objętości.

Ogólnie rzecz biorąc, można by zdecydować się na korzystanie z list (list) ze względu na ich elastyczność wielkości. Co więcej, dokumentacja msdn twierdzi, że listy używają tablicy wewnętrznie i powinny działać równie szybko (szybkie spojrzenie z Reflector potwierdza to). Jednak zawsze wiąże się to z pewnymi kosztami ogólnymi.

Czy ktoś faktycznie to zmierzył? czy iteracja 6 milionów razy przez listę zajęłaby tyle samo czasu co tablica?


3
Pomijając problemy z wydajnością, wolę używać tablic niż list ze względu na ich stały rozmiar (w przypadkach, w których zmiana liczby elementów nie jest oczywiście wymagana). Kiedy czytam istniejący kod, uważam, że pomocna jest szybka informacja, że ​​element jest zmuszony mieć stały rozmiar, zamiast konieczności sprawdzania kodu w dalszej części funkcji.
Warren Stevens

2
T[]vs. List<T>może mieć duży wpływ na wydajność. Właśnie zoptymalizowałem bardzo (zagnieżdżoną) aplikację intensywnie korzystającą z pętli, aby przejść z list do tablic w .NET 4.0. Spodziewałem się poprawy o 5–10%, ale przyspieszyłem o ponad 40%! Żadnych innych zmian niż przejście bezpośrednio z listy do tablicy. Wszystkie wyliczenia zostały wykonane za pomocą foreachinstrukcji. Na podstawie odpowiedzi Marca Gravella wygląda na to, że foreachz List<T>jest szczególnie źle.
Sos Specjalny

Odpowiedzi:


221

Bardzo łatwy do zmierzenia ...

W niewielkiej liczbie kodu przetwarzania o wąskiej pętli, w którym wiem, że długość jest ustalona , używam tablic do tego bardzo małego mikrooptymalizacji; tablice mogą być nieznacznie szybsze, jeśli użyjesz indeksu / dla formularza - ale IIRC wierzy, że zależy to od rodzaju danych w tablicy. Ale chyba, że potrzebujesz mikrooptymalizować, zachowaj prostotę i używaj List<T>itp.

Oczywiście dotyczy to tylko odczytu wszystkich danych; słownik byłby szybszy dla wyszukiwań opartych na kluczach.

Oto moje wyniki za pomocą „int” (druga liczba to suma kontrolna, aby sprawdzić, czy wszystkie wykonały tę samą pracę):

(edytowany, aby naprawić błąd)

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

na podstawie zestawu testowego:

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
Ciekawe szczegóły: Oto czasy WYDAWANIE / DEBUGOWANIE na moim urządzeniu (.net 3.5 sp1): 0,92, 0,80, 0,96, 0,93; co mówi mi, że w maszynie wirtualnej jest pewna inteligencja do optymalizacji pętli Array / for o około 10% lepiej niż w innych przypadkach.
David Schmitt

2
Tak, istnieje optymalizacja JIT dla tablicy / for. Właściwie miałem wrażenie, że zawiera on futerał Length (ponieważ wie, że jest naprawiony), dlatego też nie wyciągnąłem go jako pierwszy (w przeciwieństwie do Listu, w którym to zrobiłem). Dziękuję za aktualizację.
Marc Gravell

2
Dziwne. Moje bardzo podobne testy nie wykazują znaczącej różnicy między forami i foreach przy użyciu tablic. Zbadam dokładnie w poście na blogu ze wskaźnikiem, który inni mogą uruchomić i wyślą mi wyniki dla ...
Jon Skeet

1
Otrzymuję radykalnie różne wyniki, jeśli używam innej zmiennej dla chk dla każdego testu (chk1, chk2, chk3, chk4). List / for = 1303ms, Array / for = 433ms. Jakieś pomysły dlaczego?
Jon

4
Link wspomniany w powyższym komentarzu Jona do bloga Jona Skeeta został zerwany. Poniżej znajduje się zaktualizowany link. codeblog.jonskeet.uk/2009/01/29/…
Josh DeLong,

88

Podsumowanie:

  • Tablica musi używać:

    • Tak często jak to możliwe. Jest szybki i zajmuje najmniejszy zakres pamięci RAM dla tej samej ilości informacji.
    • Jeśli znasz dokładną liczbę potrzebnych komórek
    • Jeśli dane zapisane w tablicy <85000 b (85000/32 = 2656 elementów dla danych całkowitych)
    • W razie potrzeby wysoka prędkość dostępu losowego
  • Lista musi użyć:

    • W razie potrzeby dodaj komórki na końcu listy (często)
    • W razie potrzeby dodaj komórki na początku / w środku listy (NIE CZĘSTO)
    • Jeśli dane zapisane w tablicy <85000 b (85000/32 = 2656 elementów dla danych całkowitych)
    • W razie potrzeby wysoka prędkość dostępu losowego
  • LinkedList musi użyć:

    • W razie potrzeby dodaj komórki na początku / środku / końcu listy (często)

    • W razie potrzeby tylko dostęp sekwencyjny (do przodu / do tyłu)

    • Jeśli chcesz zapisać DUŻE przedmioty, ale liczba przedmiotów jest niska.

    • Lepiej nie używaj do dużej ilości przedmiotów, ponieważ używa to dodatkowej pamięci na linki.

      Jeśli nie masz pewności, czy potrzebujesz LinkedList - NIE POTRZEBUJESZ.


Więcej szczegółów:

znaczenie koloru

Tablica vs lista vs lista połączona

Znacznie więcej szczegółów:

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


Jestem nieco zdezorientowany twoim stwierdzeniem, że dodawanie listy jest stosunkowo szybkie, ale wstawianie jest wolne. Wstawianie jest również liniowe i szybsze średnio o 50% niż przedpłata.
Mike Marynowski,

1
@MikeMarynowski na liście c # otacza Array. W przypadku wstawienia do listy będziesz miał czas liniowy tylko do pewnego momentu. Po tym system utworzy nową, większą tablicę i będzie potrzebował czasu, aby skopiować elementy ze starej.
Andrew

To samo z dopłatami.
Mike Marynowski,

Operacja dodawania to po prostu wstawka o wartości 0. To najgorszy przypadek wstawiania pod względem wydajności, więc jeśli wstawianie jest powolne, dodawanie jest jeszcze wolniejsze.
Mike Marynowski,

Zarówno wstawianie, jak i dodawanie jest O (n) (amortyzowane). A prepend JEST wstawką, ale jest to najwolniejsza możliwa wstawka, ponieważ musi przesuwać WSZYSTKIE elementy na liście o jedno miejsce w górę. Wstawka w losowej lokalizacji musi jedynie przesunąć w górę elementy o wyższym indeksie niż punkt wstawienia, więc średnio 50% elementów.
Mike Marynowski,

26

Myślę, że wydajność będzie podobna. Narzutem związanym z korzystaniem z listy w porównaniu z tablicą jest IMHO podczas dodawania elementów do listy i gdy lista musi zwiększyć rozmiar tablicy, której używa wewnętrznie, gdy pojemność tablicy zostanie osiągnięta.

Załóżmy, że masz listę o pojemności 10, a następnie lista zwiększy jej pojemność, gdy będziesz chciał dodać 11 element. Możesz zmniejszyć wpływ na wydajność, inicjując pojemność listy do liczby przechowywanych elementów.

Ale, aby dowiedzieć się, czy iteracja po liście jest tak szybka, jak iteracja po tablicy, dlaczego nie przetestujesz jej?

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();

W moim systemie; iteracja po tablicy zajęła 33 ms; iteracja po liście zajęła 66 ms.

Szczerze mówiąc, nie spodziewałem się, że ta odmiana będzie tak duża. Tak więc umieściłem iterację w pętli: teraz wykonuję obie iteracje 1000 razy. Wyniki są następujące:

iteracja listy zajęła 67146 ms iteracja tablicy zajęła 40821 ms

Ta odmiana nie jest już tak duża, ale nadal ...

Dlatego uruchomiłem .NET Reflector i moduł pobierania indeksu klasy List wygląda następująco:

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

Jak widać, podczas korzystania z indeksatora Listy, Lista sprawdza, czy nie wychodzisz poza granice wewnętrznej tablicy. Ta dodatkowa kontrola wiąże się z opłatą.


Cześć Frederik, dzięki! Jak wytłumaczysz, że twoja lista zajęła dwa razy więcej czasu niż tablice. Nie tego byś się spodziewał. Czy próbowałeś zwiększyć liczbę elementów?
Boaz

1
Nie zwróciłbym this._items [index]; już rzucić wyjątek, jeśli indeks był poza zakresem? Dlaczego .NET ma tę dodatkową kontrolę, gdy wynik końcowy jest taki sam z nią lub bez niej?
John Mercier,

@John Mercier, porównanie jest z rozmiarem listy (liczba aktualnie zawartych elementów), która jest inna i prawdopodobnie mniejsza niż pojemność tablicy _items. Tablica jest przydzielana z nadmiarem pojemności, aby przyspieszyć dodawanie przyszłych elementów, nie wymagając ponownego przydzielania dla każdego dodania.
Trasvi

21

jeśli pobierasz tylko jedną wartość z jednego z nich (nie w pętli), oba sprawdzają granice (pamiętasz kod zarządzany), tylko lista robi to dwa razy. Zobacz uwagi później, dlaczego prawdopodobnie nie jest to wielka sprawa.

Jeśli używasz własnego dla (int int i = 0; i <x. [Length / Count]; i ++), kluczowa różnica jest następująca:

  • Szyk:
    • sprawdzanie granic zostało usunięte
  • Listy
    • sprawdzanie granic jest wykonywane

Jeśli używasz foreach, kluczowa różnica jest następująca:

  • Szyk:
    • żaden obiekt nie jest przydzielony do zarządzania iteracją
    • sprawdzanie granic zostało usunięte
  • List za pomocą zmiennej znanej jako List.
    • zmienna zarządzania iteracją jest przydzielana stosowo
    • sprawdzanie granic jest wykonywane
  • Wyświetl listę za pomocą zmiennej znanej jako IList.
    • zmienna zarządzania iteracją jest przydzielana do sterty
    • sprawdzanie granic jest również wykonywane Wartości list nie mogą być zmieniane podczas foreach, podczas gdy tablice mogą być.

Sprawdzanie granic często nie jest wielkim problemem (szczególnie jeśli korzystasz z procesora z głęboką prognozą potoku i rozgałęzienia - normą na większość dni), ale tylko twoje własne profilowanie może ci powiedzieć, czy to problem. Jeśli znajdujesz się w części kodu, w której unikasz alokacji sterty (dobrym przykładem są biblioteki lub implementacje kodu mieszającego), to upewnienie się, że zmienna jest wpisana jako Lista nie IList, pozwoli uniknąć pułapki. Jak zawsze profil, jeśli ma to znaczenie.


11

[ Zobacz także to pytanie ]

Zmodyfikowałem odpowiedź Marca, aby używała rzeczywistych liczb losowych i faktycznie wykonywała tę samą pracę we wszystkich przypadkach.

Wyniki:

         dla foreach
Tablica: 1575ms 1575ms (+ 0%)
Lista: 1630 ms 2627 ms (+ 61%)
         (+ 3%) (+ 67%)

(Suma kontrolna: -1000038876)

Opracowano jako wydanie na podstawie VS 2008 SP1. Działa bez debugowania na Q6600@2.40GHz, .NET 3.5 SP1.

Kod:

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

To dziwne - właśnie uruchomiłem twój dokładny kod, zbudowany z linii poleceń (3.5SP1) za pomocą / o + / debug-, a moje wyniki to: list / for: 1524; tablica / dla: 1472; lista / foreach: 4128; tablica / foreach: 1484.
Jon Skeet

Mówisz, że to zostało skompilowane jako wydanie - czy mogę po prostu sprawdzić, czy go uruchomiłeś, a nie debugować? Głupie pytanie, wiem, ale inaczej nie potrafię wyjaśnić rezultatów ...
Jon Skeet

2

Pomiary są dobre, ale będziesz uzyskiwać znacznie różne wyniki w zależności od tego, co robisz dokładnie w swojej wewnętrznej pętli. Zmierz swoją sytuację. Jeśli używasz wielowątkowości, samo to jest nietrywialne.


2

Rzeczywiście, jeśli wykonasz kilka skomplikowanych obliczeń w pętli, wydajność indeksatora tablic w porównaniu z indeksatorem list może być tak nieznacznie mała, że ​​ostatecznie nie ma to znaczenia.


2

Oto jeden, który używa Słowników, 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

Nie próbuj zwiększać pojemności poprzez zwiększenie liczby elementów.

Występ

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

Zaczynam przeskalowywać tablicę 60k razy, będzie ona powolna ... Z pewnością w świecie rzeczywistym podejściem byłoby po prostu sprawdzenie, ile dodatkowych slotów potrzebujesz, zmiana rozmiaru na długość + 60k, a następnie przejście przez wstawki.
tobriand

Zmiana rozmiaru tablicy jest bardzo szybka, jeśli podwajasz rozmiar za każdym razem, gdy potrzebujesz więcej miejsca. Odkryłem, że zajmuje to tyle samo czasu, co po prostu zmiana rozmiaru raz po początkowej deklaracji. To daje elastyczność listy i większą prędkość tablicy.
user1318499

2

Martwiłem się, że testy porównawcze zamieszczone w innych odpowiedziach nadal pozostawią miejsce dla kompilatora w celu optymalizacji, eliminacji lub scalania pętli, więc napisałem taki, który:

  • Używane nieprzewidywalne dane wejściowe (losowe)
  • Uruchamia obliczone z wynikiem wydrukowanym na konsoli
  • Zmienia dane wejściowe przy każdym powtórzeniu

W rezultacie bezpośrednia tablica ma około 250% lepszą wydajność niż dostęp do tablicy owiniętej w IList:

  • Dostęp do 1 miliarda tablic: 4000 ms
  • Dostęp do 1 miliarda dostępów: 10000 ms
  • 100 milionów dostępów do macierzy: 350 ms
  • Dostęp do listy 100 milionów: 1000 ms

Oto kod:

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

Ponieważ List <> używa tablic wewnętrznie, podstawowa wydajność powinna być taka sama. Dwa powody, dla których lista może być nieco wolniejsza:

  • Aby wyszukać element na liście, wywoływana jest metoda List, która wyszukuje w podstawowej tablicy. Potrzebujesz więc dodatkowego wywołania metody. Z drugiej strony kompilator może to rozpoznać i zoptymalizować „niepotrzebne” wywołanie.
  • Kompilator może wykonać specjalne optymalizacje, jeśli zna rozmiar tablicy, czego nie może zrobić dla listy o nieznanej długości. Może to przynieść poprawę wydajności, jeśli masz tylko kilka elementów na liście.

Aby sprawdzić, czy ma to dla Ciebie znaczenie, prawdopodobnie najlepiej dopasuj opublikowane funkcje synchronizacji do listy o rozmiarze, którego planujesz użyć, i zobacz, jakie są wyniki dla twojego specjalnego przypadku.


0

Ponieważ miałem podobne pytanie, zapewniłem sobie szybki start.

Moje pytanie jest nieco bardziej szczegółowe: „jaka jest najszybsza metoda implementacji macierzy zwrotnej”

Testy przeprowadzone przez Marca Gravella pokazują wiele, ale nie dokładnie określają czas. Jego wyczucie czasu obejmuje również zapętlanie tablicy i list. Ponieważ wymyśliłem także trzecią metodę, którą chciałem przetestować, czyli „Słownik”, dla porównania, rozszerzyłem kod testowy hist.

Najpierw wykonuję test przy użyciu stałej, która daje mi pewien czas, w tym pętlę. Jest to czas „goły”, z wyłączeniem faktycznego dostępu. Następnie wykonuję test, uzyskując dostęp do struktury tematu, co daje mi i taktowanie, zapętlenie i rzeczywisty dostęp.

Różnica między taktowaniem „nagim” a czasem „wykluczonym z góry” daje mi wskazanie taktowania „dostępu do struktury”.

Ale jak dokładny jest ten czas? Podczas testu Windows wykona krojenie czasu dla shure. Nie mam żadnych informacji na temat podziału czasu, ale zakładam, że jest on równomiernie rozłożony podczas testu i rzędu dziesiątek ms, co oznacza, że ​​dokładność pomiaru czasu powinna być rzędu +/- 100 ms lub więcej. Trochę przybliżone oszacowanie? W każdym razie źródło systematycznego błędu mearure.

Ponadto testy przeprowadzono w trybie „Debugowanie” bez optymalizacji. W przeciwnym razie kompilator może zmienić rzeczywisty kod testowy.

Otrzymuję więc dwa wyniki, jeden dla stałej oznaczonej „(c)”, a drugi dla dostępu oznaczony „(n)”, a różnica „dt” mówi mi, ile czasu zajmuje faktyczny dostęp.

A oto wyniki:

          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

Przy lepszych oszacowaniach błędów synchronizacji (jak usunąć systematyczny błąd pomiaru z powodu podziału czasu?) Można by powiedzieć więcej o wynikach.

Wygląda na to, że Lista / Foreach ma najszybszy dostęp, ale koszty ogólne go zabijają.

Różnica między List / for i List / foreach jest dziwna. Może w grę wchodzi gotówka?

Ponadto, aby uzyskać dostęp do tablicy, nie ma znaczenia, czy używasz forpętli czy foreachpętli. Wyniki pomiaru czasu i jego dokładność sprawiają, że wyniki są „porównywalne”.

Korzystanie ze słownika jest zdecydowanie najwolniejsze, zastanawiałem się tylko nad tym, ponieważ po lewej stronie (indeksator) mam rzadką listę liczb całkowitych, a nie zakres używany w tych testach.

Oto zmodyfikowany kod testowy.

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

W niektórych krótkich testach odkryłem, że kombinacja tych dwóch jest lepsza w tak zwanej dość intensywnej matematyce:

Rodzaj: List<double[]>

Czas: 00: 00: 05.1861300

Rodzaj: List<List<double>>

Czas: 00: 00: 05.7941351

Rodzaj: double[rows * columns]

Czas: 00: 00: 06.0547118

Uruchamianie kodu:

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());

Chciałbym, abyśmy mieli jakieś najwyższej klasy klasy akceleracji sprzętowej, takie jak zespół .NET z System.Numerics.Vectorsklasą!

C # może być najlepszym językiem ML z odrobiną więcej pracy w tym obszarze!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.