Java: tablica Wielowymiarowa vs jednowymiarowa
Na przykład:
-
A)
int [x][y][z]
Vs
B)
int[x*y*z]
Początkowo myślałem, że pójdę z) dla prostoty.
Wiem, że Java nie przechowuje tablic liniowo w pamięci tak jak C, ale jakie to ma konsekwencje dla mojego programu?
4 answers
Zwykle najlepszą rzeczą do zrobienia podczas wyszukiwania anwers dla takich pytań jest sprawdzenie, jak wybory są kompilowane do JVM bytecode:
multi = new int[50][50];
single = new int[2500];
To jest przetłumaczone na:
BIPUSH 50
BIPUSH 50
MULTIANEWARRAY int[][] 2
ASTORE 1
SIPUSH 2500
NEWARRAY T_INT
ASTORE 2
Więc, jak widać, JVM już wie, że mówimy o macierzy wielowymiarowej.
Dalej:
for (int i = 0; i < 50; ++i)
for (int j = 0; j < 50; ++j)
{
multi[i][j] = 20;
single[i*50+j] = 20;
}
To jest przetłumaczone (pomijając cykle) na:
ALOAD 1: multi
ILOAD 3: i
AALOAD
ILOAD 4: j
BIPUSH 20
IASTORE
ALOAD 2: single
ILOAD 3: i
BIPUSH 50
IMUL
ILOAD 4: j
IADD
BIPUSH 20
IASTORE
Więc, jak widać, tablica wielowymiarowa jest traktowana wewnętrznie w maszynie wirtualnej, no overhead generowane przez bezużyteczne instrukcje, podczas korzystania z jednego używa więcej instrukcji, ponieważ offset jest obliczany ręcznie.
Nie sądzę, że wydajność będzie takim problemem.
EDIT:
Zrobiłem kilka prostych benchmarków, aby zobaczyć, co tu się dzieje.
Postanowiłem spróbować różnych przykładów:
odczyt liniowy,
zapis liniowy,
i losowy dostęp.
Czasy są wyrażone w milisekach (i obliczane za pomocą System.nanoTime()
.
Oto wyniki:
Zapis liniowy
- Rozmiar: 100x100 (10000)
- Multi: 5.786591
- singiel: 6.131748
Rozmiar: 200x200 (40000)
- Multi: 1.216366
- Single: 0.782041
- Multi: 7.177029
- singiel: 3.667017
- Multi: 30.508131
- singiel: 18.064592
- Multi: 185.3548
- pojedynczy: 155.590313
- Multi: 955.5299
- singiel: 923.264417
- Multi: 4084.798753
- singiel: 4015.448829
Odczyt liniowy
- Rozmiar: 100x100 (10000)
- Multi: 5.241338
- pojedynczy: 5.135957
- Multi: 0.080209
- Single: 0.044371
- Multi: 0.088742
- Single: 0.084476
- Multi: 0.232095
- Single: 0.167671
- Multi: 0.481683
- Single: 0.33321
- Multi: 1.222339
- Single: 0.828118
- Multi: 2.496302
- Single: 1.650691
Losowy odczyt
- Rozmiar: 100x100 (10000)
- Multi: 22.317393
- singiel: 8.546134
- Multi: 32.287669
- singiel: 11.022383
- Multi: 189.542751
- Single: 68.181343
- Multi: 1124.78609
- pojedynczy: 272.235584
- Multi: 6814.477101
- singiel: 1091.998395
- Multi: 50051.306239
- singiel: 7028.422262
Losowy jest trochę mylący, ponieważ generuje 2 losowe liczby dla wielowymiarowej tablicy podczas gdy tylko jeden dla jednowymiarowych (a PNRGs może zużywać trochę procesora).
Pamiętaj, że próbowałem pozwolić JIT pracy przez benchmarking dopiero po 20 biegu tej samej pętli. Dla kompletności moja maszyna wirtualna java jest następująca:
Java version "1.6.0_17" Java (TM) SE Runtime Environment (build 1.6.0_17-b04) W przeciwieństwie do innych maszyn wirtualnych, nie można ich używać.]}
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
2019-10-22 22:12:07
Na obecnych procesorach dostęp do pamięci niebuforowanej jest setki razy wolniejszy od arytmetyki (zobacz tę prezentację i przeczytaj to, co każdy programista powinien wiedzieć o pamięci). Opcja a) spowoduje około 3 wyszukiwanie pamięci, podczas gdy opcja b) spowoduje około 1 wyszukiwanie pamięci. Również algorytmy wstępnego ustawiania procesora mogą nie działać. Tak więc opcja b) może być szybsza w niektórych sytuacjach(jest to hot spot i tablica nie mieści się w pamięci podręcznej procesora). Ile szybciej? - to zależy od aplikacji.
Osobiście najpierw użyłbym opcji a), ponieważ spowoduje to prostszy kod. Jeśli profiler pokazuje, że dostęp do tablicy jest wąskim gardłem, to przekonwertowałbym go do opcji b), tak że istnieje para metod pomocniczych do odczytu i zapisu wartości tablicy (w ten sposób niechlujny kod będzie ograniczony do tych dwóch metod).
Zrobiłem benchmark do porównywania 3-wymiarowych tablic int (Kolumna "Multi") do odpowiednika 1-wymiarowe tablice int (Kolumna" pojedyncza"). Kod jest tutaj i testy tutaj. Uruchomiłem go na 64-bitowym jdk1.6.0_18, Windows 7 x64, Core 2 Quad Q6600 @ 3.0 GHz, 4 GB DDR2, używając opcji JVM -server -Xmx3G -verbose:gc -XX:+PrintCompilation
(usunąłem wyjście debugowania z poniższych wyników). Wyniki były następujące:
Out of 20 repeats, the minimum time in milliseconds is reported.
Array dimensions: 100x100x100 (1000000)
Multi Single
Seq Write 1 1
Seq Read 1 1
Random Read 99 90 (of which generating random numbers 59 ms)
Array dimensions: 200x200x200 (8000000)
Multi Single
Seq Write 14 13
Seq Read 11 8
Random Read 1482 1239 (of which generating random numbers 474 ms)
Array dimensions: 300x300x300 (27000000)
Multi Single
Seq Write 53 46
Seq Read 34 24
Random Read 5915 4418 (of which generating random numbers 1557 ms)
Array dimensions: 400x400x400 (64000000)
Multi Single
Seq Write 123 111
Seq Read 71 55
Random Read 16326 11144 (of which generating random numbers 3693 ms)
To pokazuje, że tablica 1-wymiarowa jest szybsza. Chociaż różnice są tak małe, że dla 99% aplikacji nie będzie to zauważalne.
Zrobiłem też kilka pomiarów, aby oszacować narzut generowania liczb losowych w Random Read benchmark przez zastąpienie preventOptimizingAway += array.get(x, y, z);
przez preventOptimizingAway += x * y * z;
i dodanie pomiarów do powyższej tabeli wyników ręcznie. Generowanie liczb losowych zajmuje 1/3 lub mniej całkowitego czasu badania losowego odczytu, więc dostęp do pamięci dominuje w badaniu zgodnie z oczekiwaniami. Interesujące byłoby powtórzenie tego wzorca z tablicami o wymiarach 4 i więcej. Prawdopodobnie zwiększyłoby to różnicę prędkości, ponieważ wielowymiarowa tablica jest najwyższe poziomy będą pasowały do pamięci podręcznej procesora, a tylko pozostałe poziomy będą wymagały wyszukiwania pamięci.
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
2013-03-24 09:10:02
Użyj pierwszego wariantu (3-wymiarowego), ponieważ jest łatwiejszy do zrozumienia i jest mniej szans na popełnienie błędu logicznego (zwłaszcza jeśli używasz go do modelowania przestrzeni 3-wymiarowej)
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
2010-03-24 23:08:34
Jeśli wybierzesz tę ostatnią trasę, będziesz musiał wykonać arytmetykę dla każdego dostępu do tablicy. To będzie podatne na ból i błędy (chyba, że owiniesz go w klasie zapewniającej tę funkcjonalność).
Nie wierzę, że jest jakaś (znacząca) optymalizacja w wyborze tablicy płaskiej (zwłaszcza biorąc pod uwagę arytmetykę wziętą do indeksu). Jak zawsze w przypadku optymalizacji, musisz wykonać kilka pomiarów i określić, czy naprawdę warto.
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
2010-03-24 23:08:26