Jakie są różnice między wielowymiarową tablicą a tablicą tablic w C#?

Jakie są różnice między tablicami wielowymiarowymi double[,] i tablicami tablic double[][] W C#?

Jeśli jest różnica, co jest najlepsze dla każdego z nich?

Author: Preethi, 2009-02-28

9 answers

Array of arrays (postrzępione tablice) są szybsze niż tablice wielowymiarowe i mogą być wykorzystywane bardziej efektywnie. Tablice wielowymiarowe mają ładniejszą składnię.

Jeśli napiszesz prosty kod używając postrzępionych i wielowymiarowych tablic, a następnie przejrzysz skompilowany zespół za pomocą DISASSEMBLERA IL, zobaczysz, że przechowywanie i pobieranie z postrzępionych (lub jednowymiarowych) tablic są prostymi instrukcjami IL, podczas gdy te same operacje dla wielowymiarowych tablic są wywołaniami metod, które zawsze są wolniejsze.

Rozważ następujące metody:

static void SetElementAt(int[][] array, int i, int j, int value)
{
    array[i][j] = value;
}

static void SetElementAt(int[,] array, int i, int j, int value)
{
    array[i, j] = value;
}

Ich IL będą następujące:

.method private hidebysig static void  SetElementAt(int32[][] 'array',
                                                    int32 i,
                                                    int32 j,
                                                    int32 'value') cil managed
{
  // Code size       7 (0x7)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldelem.ref
  IL_0003:  ldarg.2
  IL_0004:  ldarg.3
  IL_0005:  stelem.i4
  IL_0006:  ret
} // end of method Program::SetElementAt

.method private hidebysig static void  SetElementAt(int32[0...,0...] 'array',
                                                    int32 i,
                                                    int32 j,
                                                    int32 'value') cil managed
{
  // Code size       10 (0xa)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldarg.2
  IL_0003:  ldarg.3
  IL_0004:  call       instance void int32[0...,0...]::Set(int32,
                                                           int32,
                                                           int32)
  IL_0009:  ret
} // end of method Program::SetElementAt

Przy użyciu postrzępionych tablic można łatwo wykonać takie operacje jak zamiana wierszy i zmiana rozmiaru wierszy. Być może w niektórych przypadkach użycie tablic wielowymiarowych będzie bezpieczniejsze, ale nawet Microsoft FxCop mówi, że tablice postrzępione powinny być używane zamiast wielowymiarowych, gdy używasz ich do analizy projektów.

 290
Author: okutane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-22 06:01:41

Wielowymiarowa tablica tworzy ładny układ pamięci liniowej, podczas gdy tablica postrzępiona implikuje kilka dodatkowych poziomów indrection.

Szukanie wartości jagged[3][6] w postrzępionej tablicy var jagged = new int[10][5] działa tak: wyszukaj element w indeksie 3 (który jest tablicą) i wyszukaj element w indeksie 6 w tej tablicy (który jest wartością). Dla każdego wymiaru w tym przypadku istnieje dodatkowe spojrzenie w górę (jest to kosztowny wzór dostępu do pamięci).

Wielowymiarowa tablica jest ułożona liniowo w pamięci, rzeczywista wartość znajduje się mnożąc razem indeksy. Jednak biorąc pod uwagę tablicę var mult = new int[10,30], właściwość Length tej wielowymiarowej tablicy zwraca całkowitą liczbę elementów, tj. 10 * 30 = 300.

Właściwość Rank tablicy postrzępionej jest zawsze równa 1, ale tablica wielowymiarowa może mieć dowolną rangę. Do uzyskania długości każdego wymiaru można użyć metody GetLength dowolnej tablicy. Dla tablicy wielowymiarowej w tym przykładzie mult.GetLength(1) zwraca 30.

Indeksowanie wielowymiarowa tablica jest szybsza. np. biorąc pod uwagę wielowymiarową tablicę w tym przykładzie mult[1,7] = 30 * 1 + 7 = 37, zdobądź element w indeksie 37. Jest to lepszy wzorzec dostępu do pamięci, ponieważ dotyczy tylko jednej lokalizacji pamięci, która jest adresem bazowym tablicy.

Wielowymiarowa tablica przydziela zatem ciągły blok pamięci, podczas gdy postrzępiona tablica nie musi być kwadratowa, np. jagged[1].Length nie musi być równa jagged[2].Length, co byłoby prawdziwe dla dowolnego wielowymiarowego / align = "left" /

Wydajność

Wydajność, wielowymiarowe tablice powinny być szybsze. Dużo szybciej, ale ze względu na naprawdę złą implementację CLR nie są.

 23.084  16.634  15.215  15.489  14.407  13.691  14.695  14.398  14.551  14.252 
 25.782  27.484  25.711  20.844  19.607  20.349  25.861  26.214  19.677  20.171 
  5.050   5.085   6.412   5.225   5.100   5.751   6.650   5.222   6.770   5.305 

Pierwszy wiersz to timingi postrzępionych tablic, drugi pokazuje tablice wielowymiarowe, a trzeci, tak powinno być. Program jest pokazany poniżej, FYI to było testowane uruchomienie mono. (Czasy okien są znacznie różne, głównie ze względu na różnice w implementacji CLR).

On windows, czasy postrzępionych tablic są znacznie lepsze, tak samo jak moja własna interpretacja tego, jak powinna wyglądać wielowymiarowa tablica, patrz'Single ()'. Niestety Windows JIT-kompilator jest naprawdę głupi, a to niestety utrudnia te dyskusje o wydajności, jest zbyt wiele niespójności.

To są timingi, które mam na Windowsie, to samo tutaj, pierwszy wiersz to postrzępione tablice, drugi wielowymiarowy, a trzeci moja własna implementacja wielowymiarowe, zauważ, o ile wolniej jest to w windows w porównaniu do mono.

  8.438   2.004   8.439   4.362   4.936   4.533   4.751   4.776   4.635   5.864
  7.414  13.196  11.940  11.832  11.675  11.811  11.812  12.964  11.885  11.751
 11.355  10.788  10.527  10.541  10.745  10.723  10.651  10.930  10.639  10.595

Kod źródłowy:

using System;
using System.Diagnostics;
static class ArrayPref
{
    const string Format = "{0,7:0.000} ";
    static void Main()
    {
        Jagged();
        Multi();
        Single();
    }

    static void Jagged()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var jagged = new int[dim][][];
            for(var i = 0; i < dim; i++)
            {
                jagged[i] = new int[dim][];
                for(var j = 0; j < dim; j++)
                {
                    jagged[i][j] = new int[dim];
                    for(var k = 0; k < dim; k++)
                    {
                        jagged[i][j][k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }

    static void Multi()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var multi = new int[dim,dim,dim];
            for(var i = 0; i < dim; i++)
            {
                for(var j = 0; j < dim; j++)
                {
                    for(var k = 0; k < dim; k++)
                    {
                        multi[i,j,k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }

    static void Single()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var single = new int[dim*dim*dim];
            for(var i = 0; i < dim; i++)
            {
                for(var j = 0; j < dim; j++)
                {
                    for(var k = 0; k < dim; k++)
                    {
                        single[i*dim*dim+j*dim+k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }
}
 177
Author: John Leidegren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-25 16:44:37

Po prostu wielowymiarowe tablice są podobne do tabeli w DBMS.
Array of Array (jagged array) pozwala każdemu elementowi trzymać inną tablicę tego samego typu o zmiennej długości.

Jeśli więc jesteś pewien, że struktura danych wygląda jak tabela (stałe wiersze/kolumny), możesz użyć tablicy wielowymiarowej. Postrzępione tablice są stałymi elementami i każdy element może posiadać tablicę o zmiennej długości

Np. Psuedocode:

int[,] data = new int[2,2];
data[0,0] = 1;
data[0,1] = 2;
data[1,0] = 3;
data[1,1] = 4;

Pomyśl o powyższym jako o 2x2 Tabela:

1 | 2
3 | 4
int[][] jagged = new int[3][]; 
jagged[0] = new int[4] {  1,  2,  3,  4 }; 
jagged[1] = new int[2] { 11, 12 }; 
jagged[2] = new int[3] { 21, 22, 23 }; 

Pomyśl o powyższym, ponieważ każdy wiersz ma zmienną liczbę kolumn:

 1 |  2 |  3 | 4
11 | 12
21 | 22 | 23
 61
Author: shahkalpesh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-25 17:47:47

Przedmowa: Ten komentarz ma na celu adres odpowiedzi udzielonej przez okutane , ale z powodu głupiego systemu reputacji SO, nie mogę go zamieścić tam, gdzie jego miejsce.

Twoje twierdzenie, że jedna jest wolniejsza od drugiej z powodu wywołania metody, nie jest poprawne. Jeden jest wolniejszy od drugiego ze względu na bardziej skomplikowane algorytmy sprawdzania granic. Możesz to łatwo zweryfikować, patrząc nie na IL, ale na skompilowany zespół. Na przykład na mojej instalacji 4.5, dostęp do elementu (poprzez wskaźnik w edx) przechowywanego w dwuwymiarowej tablicy wskazywanej przez ecx z indeksami przechowywanymi w eax i edx wygląda następująco:

sub eax,[ecx+10]
cmp eax,[ecx+08]
jae oops //jump to throw out of bounds exception
sub edx,[ecx+14]
cmp edx,[ecx+0C]
jae oops //jump to throw out of bounds exception
imul eax,[ecx+0C]
add eax,edx
lea edx,[ecx+eax*4+18]

Tutaj możesz zobaczyć, że nie ma żadnych kosztów z wywołań metod. Sprawdzanie granic jest po prostu bardzo zawiłe dzięki możliwości niezerowych indeksów, co jest funkcjonalnością niedostępną w przypadku postrzępionych tablic. Jeśli usuniemy sub,cmp i JMP dla niezerowych przypadków, kod praktycznie rozwiązuje się do (x*y_max+y)*sizeof(ptr)+sizeof(array_header). Obliczenie to jest mniej więcej tak szybko (jeden mnożnik może zostać zastąpiony przesunięciem, ponieważ to jest cały powód, dla którego wybieramy bajty jako potęgi dwóch bitów), jak Wszystko inne dla losowego dostępu do elementu.

Kolejną komplikacją jest to, że jest wiele przypadków, w których nowoczesny kompilator optymalizuje zagnieżdżone ograniczenia-sprawdzając dostęp do elementu podczas iteracji tablicy jednowymiarowej. Rezultatem jest kod, który w zasadzie po prostu przesuwa wskaźnik indeksu nad sąsiadującą pamięcią / align = "left" / Naiwna iteracja nad tablicami wielowymiarowymi zazwyczaj wymaga dodatkowej warstwy zagnieżdżonej logiki, więc kompilator jest mniej podatny na optymalizację operacji. Tak więc, nawet jeśli narzut sprawdzający granice dostępu do pojedynczego elementu zamortyzuje się do stałego trybu runtime w odniesieniu do wymiarów i rozmiarów tablicy, wykonanie prostego przypadku testowego do zmierzenia różnicy może potrwać wiele razy dłużej.

 31
Author: Eglin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-29 19:19:42

Chciałbym zaktualizować na ten temat, ponieważ w . Net Core wielowymiarowe tablice są szybsze niż postrzępione tablice . Przeprowadziłem testy z Johna Leidegrena i oto wyniki na.Net Core 2.0 preview 2. Zwiększyłem wartość wymiaru, aby wszelkie możliwe wpływy z aplikacji w tle były mniej widoczne.

Debug (code optimalization disabled)
Running jagged 
187.232 200.585 219.927 227.765 225.334 222.745 224.036 222.396 219.912 222.737 

Running multi-dimensional  
130.732 151.398 131.763 129.740 129.572 159.948 145.464 131.930 133.117 129.342 

Running single-dimensional  
 91.153 145.657 111.974  96.436 100.015  97.640  94.581 139.658 108.326  92.931 


Release (code optimalization enabled)
Running jagged 
108.503 95.409 128.187 121.877 119.295 118.201 102.321 116.393 125.499 116.459 

Running multi-dimensional 
 62.292  60.627  60.611  60.883  61.167  60.923  62.083  60.932  61.444  62.974 

Running single-dimensional 
 34.974  33.901  34.088  34.659  34.064  34.735  34.919  34.694  35.006  34.796 

Zajrzałem do demontażu i oto co znalazłem

jagged[i][j][k] = i * j * k; potrzebne 34 instrukcje do wykonania

multi[i, j, k] = i * j * k; potrzebne 11 instrukcji do execute

single[i * dim * dim + j * dim + k] = i * j * k; potrzebne 23 instrukcje do wykonania

Nie byłem w stanie zidentyfikować, dlaczego tablice jednowymiarowe są nadal szybsze niż wielowymiarowe, ale domyślam się, że ma to związek z pewną optymalizacją wykonaną na CPU

 14
Author: adsamcik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-31 07:46:56

Tablice wielowymiarowe są (n-1) macierzami wymiarowymi.

Więc int[,] square = new int[2,2] jest macierzą kwadratową 2x2, {[1] } jest macierzą sześcian-kwadrat 3x3. Proporcjonalność nie jest wymagana.

Postrzępione Tablice to po prostu tablica tablic - tablica, w której każda komórka zawiera tablicę.

Więc MDA są proporcjonalne, JD może nie! Każda komórka może zawierać tablicę o dowolnej długości!
 11
Author: abatishchev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-28 20:23:18

Mogło to być wspomniane w powyższych odpowiedziach, ale nie jawnie: w przypadku tablicy postrzępionej możesz użyć array[row] do odwołania całego wiersza danych, ale nie jest to dozwolone dla tablic multi-D.

 5
Author: lznt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-02 21:46:06

Oprócz innych odpowiedzi, zauważ, że tablica wielowymiarowa jest przydzielana jako jeden duży, masywny obiekt na stercie. Ma to pewne implikacje:

  1. niektóre wielowymiarowe tablice zostaną przydzielone na stercie dużych obiektów (Large Object Heap, LOH), gdzie ich odpowiedniki z postrzępionymi tablicami inaczej by nie miały.
  2. GC będzie musiał znaleźć pojedynczy przylegający wolny blok pamięci, aby przydzielić wielowymiarową tablicę, podczas gdy postrzępiona tablica może być w stanie wypełnić luki spowodowane fragmentacją sterty... zazwyczaj nie jest to problem w. Net z powodu zagęszczania, ale LOH nie jest domyślnie zagęszczany(musisz o to pytać i musisz pytać za każdym razem, gdy tego chcesz).
  3. będziesz chciał przyjrzeć się <gcAllowVeryLargeObjects> dla tablic wielowymiarowych sposób zanim problem kiedykolwiek pojawi się, jeśli kiedykolwiek używasz tylko postrzępionych tablic.
 1
Author: Joe Amenta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 17:53:40

Analizuję pliki. il generowane przez ildasm w celu zbudowania bazy asemnblies, klas, metod i procedur składowanych do użycia podczas konwersji. Natknąłem się na następujące, które złamały moje parsowanie.

.method private hidebysig instance uint32[0...,0...] 
        GenerateWorkingKey(uint8[] key,
                           bool forEncryption) cil managed
[11]} The Book Expert. NET 2.0 IL Assembler, by Serge Lidin, Apress, published 2006, Chapter 8, Primitive Types and Signatures, PP. 149-150 wyjaśnia.

<type>[] oznacza Wektor <type>,

<type>[<bounds> [<bounds>**] ] jest określana tablicą <type>

** środki mogą być powtórzone, [ ] oznacza opcjonalne.

Przykłady: Let <type> = int32.

1) int32[...,...] jest dwuwymiarową tablicą niezdefiniowanych dolnych granic i rozmiarów

2) int32[2...5] jest jednowymiarową tablicą o dolnej granicy 2 i wielkości 4.

3) int32[0...,0...] jest dwuwymiarową tablicą dolnych granic 0 i niezdefiniowanego rozmiaru.

Tom

 0
Author: Thomas C Hutchinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-12 20:19:09