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?

Author: 0xCursor, 2010-03-24

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
    Rozmiar: 500x500 (250000)
    • Multi: 7.177029
    • singiel: 3.667017
    Rozmiar: 1000x1000 (1000000)
    • Multi: 30.508131
    • singiel: 18.064592
    Rozmiar: 2000x2000 (4000000)
    • Multi: 185.3548
    • pojedynczy: 155.590313
    Rozmiar: 5000x5000 (25000000)
    • Multi: 955.5299
    • singiel: 923.264417
    Rozmiar: 10000x10000 (100000000)
    • Multi: 4084.798753
    • singiel: 4015.448829

Odczyt liniowy

    Rozmiar: 100x100 (10000)
    • Multi: 5.241338
    • pojedynczy: 5.135957
    Rozmiar: 200x200 (40000)
    • Multi: 0.080209
    • Single: 0.044371
    Rozmiar: 500x500 (250000)
    • Multi: 0.088742
    • Single: 0.084476
    Rozmiar: 1000x1000 (1000000)
    • Multi: 0.232095
    • Single: 0.167671
    Rozmiar: 2000x2000 (4000000)
    • Multi: 0.481683
    • Single: 0.33321
    Rozmiar: 5000x5000 (25000000)
    • Multi: 1.222339
    • Single: 0.828118
    Rozmiar: 10000x10000 (100000000)
    • Multi: 2.496302
    • Single: 1.650691

Losowy odczyt

    Rozmiar: 100x100 (10000)
    • Multi: 22.317393
    • singiel: 8.546134
    Rozmiar: 200x200 (40000)
    • Multi: 32.287669
    • singiel: 11.022383
    Rozmiar: 500x500 (250000)
    • Multi: 189.542751
    • Single: 68.181343
    Rozmiar: 1000x1000 (1000000)
    • Multi: 1124.78609
    • pojedynczy: 272.235584
    Rozmiar: 2000x2000 (4000000)
    • Multi: 6814.477101
    • singiel: 1091.998395
    Rozmiar: 5000x5000 (25000000)
    • 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ć.]}

 71
Author: Jack,
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.

 23
Author: Esko Luontola,
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)

 4
Author: Roman,
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.

 2
Author: Brian Agnew,
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