Jakie są różnice między tablicami wielowymiarowymi double[,]
a tablicami tablic double[][]
w języku C #?
Jeśli jest jakaś różnica, jakie jest najlepsze zastosowanie dla każdego z nich?
Jakie są różnice między tablicami wielowymiarowymi double[,]
a tablicami tablic double[][]
w języku C #?
Jeśli jest jakaś różnica, jakie jest najlepsze zastosowanie dla każdego z nich?
double[,]
to tablica prostokątna, adouble[][]
znana jest jako „tablica poszarpana”. Pierwszy będzie miał taką samą liczbę „kolumn” dla każdego wiersza, podczas gdy drugi (potencjalnie) będzie miał inną liczbę „kolumn” dla każdego wiersza.Odpowiedzi:
Tablice tablic (tablice postrzępione) są szybsze niż tablice wielowymiarowe i można je efektywniej wykorzystywać. Tablice wielowymiarowe mają ładniejszą składnię.
Jeśli napiszesz prosty kod za pomocą tablic postrzępionych i wielowymiarowych, a następnie sprawdzisz skompilowany zestaw za pomocą deasemblera IL, zobaczysz, że przechowywanie i pobieranie z tablic postrzępionych (lub jednowymiarowych) jest prostymi instrukcjami IL, podczas gdy te same operacje dla tablic wielowymiarowych są metodą inwokacje, które zawsze są wolniejsze.
Rozważ następujące metody:
Ich IL będzie następujący:
Korzystając z tablic poszarpanych, można łatwo wykonywać takie operacje, jak zamiana wierszy i zmiana rozmiaru wierszy. Być może w niektórych przypadkach użycie tablic wielowymiarowych będzie bardziej bezpieczne, ale nawet Microsoft FxCop mówi, że tablice postrzępione powinny być używane zamiast wielowymiarowych, gdy używasz go do analizy swoich projektów.
źródło
Wielowymiarowa tablica tworzy ładny układ pamięci liniowej, a poszarpana tablica sugeruje kilka dodatkowych poziomów pośrednich.
Wyszukiwanie wartości
jagged[3][6]
w postrzępionej tablicyvar jagged = new int[10][5]
działa w następujący sposób: Wyszukaj element o indeksie 3 (który jest tablicą) i wyszukaj element o indeksie 6 w tej tablicy (która jest wartością). W tym przypadku dla każdego wymiaru istnieje dodatkowe wyszukiwanie (jest to drogi wzorzec dostępu do pamięci).Wielowymiarowa tablica jest ułożona liniowo w pamięci, rzeczywistą wartość uzyskuje się poprzez pomnożenie indeksów. Jednak biorąc pod uwagę tablicę
var mult = new int[10,30]
,Length
właściwość tej wielowymiarowej tablicy zwraca całkowitą liczbę elementów, tj. 10 * 30 = 300.Rank
Własnością postrzępionych tablicy jest zawsze 1, ale wielowymiarowa tablica może mieć dowolną rangę.GetLength
Sposób według tablicy mogą być używane, aby długość każdego wymiaru. Dla tablicy wielowymiarowej w tym przykładziemult.GetLength(1)
zwraca 30.Indeksowanie tablicy wielowymiarowej jest szybsze. np. biorąc pod uwagę tablicę wielowymiarową w tym przykładzie
mult[1,7]
= 30 * 1 + 7 = 37, uzyskaj element o tym indeksie 37. Jest to lepszy wzorzec dostępu do pamięci, ponieważ zaangażowana jest tylko jedna lokalizacja pamięci, która jest adresem bazowym tablicy.Tablica wielowymiarowa alokuje zatem ciągły blok pamięci, podczas gdy tablica poszarpana nie musi być kwadratowa, np.
jagged[1].Length
Nie musi być równajagged[2].Length
, co byłoby prawdą dla dowolnej tablicy wielowymiarowej.Wydajność
Pod względem wydajności tablice wielowymiarowe powinny być szybsze. Są znacznie szybsze, ale z powodu naprawdę złej implementacji CLR nie są.
Pierwszy rząd to czasy poszarpanych tablic, drugi pokazuje tablice wielowymiarowe, a trzeci, cóż, tak właśnie powinno być. Program pokazano poniżej, do przetestowania w trybie mono. (Czasy okien są znacznie różne, głównie ze względu na warianty implementacji CLR).
W Windowsach czasy poszarpanych tablic są znacznie lepsze, mniej więcej tak samo jak moja własna interpretacja tego, jak powinien wyglądać tablica wielowymiarowa, patrz „Single ()”. Niestety, kompilator JIT dla systemu Windows jest naprawdę głupi, a to niestety utrudnia dyskusje na temat wydajności, jest zbyt wiele niespójności.
Są to czasy, które mam na oknach, ta sama umowa tutaj, pierwszy rząd to postrzępione tablice, drugi wielowymiarowy, a trzeci moja własna implementacja wielowymiarowa, zauważ, o ile wolniej jest to w oknach w porównaniu do mono.
Kod źródłowy:
źródło
Po prostu tablice wielowymiarowe są podobne do tabeli w DBMS.
Array of Array (postrzępiona tablica) pozwala, aby każdy element zawierał inną tablicę tego samego typu o zmiennej długości.
Jeśli więc masz pewność, że struktura danych wygląda jak tabela (stałe wiersze / kolumny), możesz użyć tablicy wielowymiarowej. Poszarpana tablica to stałe elementy i każdy element może pomieścić tablicę o zmiennej długości
Np. Kod Psuedocode:
Potraktuj powyższe jako tabelę 2x2:
Pomyśl o powyższym, ponieważ każdy wiersz ma zmienną liczbę kolumn:
źródło
Przedmowa: Ten komentarz ma na celu odpowiedzieć na odpowiedź udzieloną przez okutane , ale z powodu głupiego systemu reputacji SO nie mogę opublikować go tam, gdzie należy.
Twoje twierdzenie, że jedno jest wolniejsze od drugiego z powodu wywołań metod, jest nieprawidłowe. Jeden jest wolniejszy od drugiego z powodu bardziej skomplikowanych algorytmów sprawdzania granic. Możesz to łatwo zweryfikować, patrząc nie na IL, ale na skompilowany zestaw. Na przykład, w 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:
Tutaj możesz zobaczyć, że nie ma narzutu wywołania metod. Sprawdzanie granic jest po prostu bardzo skomplikowane dzięki możliwości indeksów niezerowych, co jest funkcją niedostępną w przypadku tablic poszarpanych. Jeśli usuniemy napisy sub, cmp i jmps dla przypadków niezerowych, kod prawie się rozwiązuje
(x*y_max+y)*sizeof(ptr)+sizeof(array_header)
. Obliczenia te są prawie tak szybkie (jeden mnożnik można zastąpić przesunięciem, ponieważ to właśnie dlatego wybieramy bajty, które mają być wielkościami dwóch bitów), jak wszystko inne w celu losowego dostępu do elementu.Inną komplikacją jest to, że istnieje wiele przypadków, w których nowoczesny kompilator zoptymalizuje sprawdzanie zagnieżdżonych granic pod kątem dostępu do elementów podczas iteracji po tablicy jednowymiarowej. Wynikiem jest kod, który zasadniczo przesuwa wskaźnik indeksu nad ciągłą pamięcią tablicy. Naiwna iteracja w przypadku tablic wielowymiarowych zazwyczaj wymaga dodatkowej warstwy zagnieżdżonej logiki, więc kompilator ma mniejsze szanse na optymalizację operacji. Tak więc, nawet jeśli narzut związany z dostępem do pojedynczego elementu amortyzuje się do stałego czasu działania w odniesieniu do wymiarów i rozmiarów tablicy, wykonanie prostej próby testowej w celu zmierzenia różnicy może potrwać wiele razy dłużej.
źródło
Chciałbym to zaktualizować, ponieważ w .NET Core tablice wielowymiarowe są szybsze niż tablice poszarpane . Przeprowadziłem testy od Johna Leidegrena i są to wyniki podglądu .NET Core 2.0. Zwiększiłem wartość wymiaru, aby wszelkie możliwe wpływy aplikacji w tle były mniej widoczne.
Zajrzałem do dezasemblacji i właśnie to znalazłem
jagged[i][j][k] = i * j * k;
potrzebne 34 instrukcje do wykonaniamulti[i, j, k] = i * j * k;
potrzebne 11 instrukcji do wykonaniasingle[i * dim * dim + j * dim + k] = i * j * k;
potrzebne 23 instrukcje do wykonaniaNie byłem w stanie zidentyfikować, dlaczego tablice jednowymiarowe były nadal szybsze niż wielowymiarowe, ale zgaduję, że ma to związek z pewną optymalizacją procesora
źródło
Tablice wielowymiarowe są matrycami (n-1) -dimension.
Tak więc
int[,] square = new int[2,2]
macierz kwadratowa 2x2,int[,,] cube = new int [3,3,3]
to macierz kwadratowa 3x3. Proporcjonalność nie jest wymagana.Poszarpane 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 być! Każda komórka może zawierać tablicę o dowolnej długości!
źródło
Mogło to zostać wspomniane w powyższych odpowiedziach, ale nie wprost: w tablicy poszarpanej można użyć
array[row]
do odwołania całego wiersza danych, ale nie jest to dozwolone w przypadku tablic multi-d.źródło
Oprócz innych odpowiedzi należy zauważyć, że tablica wielowymiarowa jest przydzielana jako jeden duży, masywny obiekt na stercie. Ma to pewne implikacje:
<gcAllowVeryLargeObjects>
na wielowymiarowych tablicach sposób zanim sprawa zostanie kiedykolwiek wymyślić, jeśli tylko kiedykolwiek wykorzystać postrzępione tablic.źródło
Analizuję pliki .il generowane przez ildasm, aby zbudować bazę danych zestawów, klas, metod i procedur przechowywanych na potrzeby konwersji. Natknąłem się na następujące, które przerwały moją analizę.
Książka Expert .NET 2.0 IL Assembler autorstwa Serge Lidin, Apress, opublikowana w 2006 r., Rozdział 8, Typy pierwotne i podpisy, s. 149-150.
<type>[]
jest nazywany wektorem<type>
,<type>[<bounds> [<bounds>**] ]
jest nazywany tablicą<type>
**
środki mogą być powtarzane,[ ]
środki opcjonalne.Przykłady: Let
<type> = int32
.1)
int32[...,...]
jest dwuwymiarową tablicą niezdefiniowanych dolnych granic i rozmiarów2)
int32[2...5]
jest jednowymiarową tablicą dolnej granicy 2 i rozmiaru 4.3)
int32[0...,0...]
jest dwuwymiarową tablicą dolnych granic 0 i nieokreślonego rozmiaru.Tomek
źródło