Apa perbedaan antara array multidimensi dan array array dalam C #?

454

Apa perbedaan antara array multidimensi double[,]dan array-of-arraydouble[][] di C #?

Jika ada perbedaan, apa gunanya yang terbaik untuk masing-masing?

ecleel
sumber
7
Yang pertama double[,]adalah array persegi panjang, sementara double[][]dikenal sebagai "array bergerigi". Yang pertama akan memiliki jumlah "kolom" yang sama untuk setiap baris, sedangkan yang kedua akan (berpotensi) memiliki jumlah "kolom" yang berbeda untuk setiap baris.
GreatAndPowerfulOz

Jawaban:

334

Array array (jagged array) lebih cepat daripada array multi-dimensi dan dapat digunakan lebih efektif. Array multidimensi memiliki sintaks yang lebih bagus.

Jika Anda menulis beberapa kode sederhana menggunakan array bergerigi dan multidimensi dan kemudian memeriksa rakitan yang dikompilasi dengan disassembler IL, Anda akan melihat bahwa penyimpanan dan pengambilan dari array bergerigi (atau dimensi tunggal) adalah instruksi IL sederhana sedangkan operasi yang sama untuk array multidimensi adalah metode doa yang selalu lebih lambat.

Pertimbangkan metode berikut:

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

IL mereka adalah sebagai berikut:

.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

Saat menggunakan array bergerigi Anda dapat dengan mudah melakukan operasi seperti pertukaran baris dan pengubahan ukuran baris. Mungkin dalam beberapa kasus penggunaan array multidimensi akan lebih aman, tetapi bahkan Microsoft FxCop mengatakan bahwa array bergerigi harus digunakan daripada multidimensi ketika Anda menggunakannya untuk menganalisis proyek Anda.

okutane
sumber
7
@ John, ukur sendiri, dan jangan membuat asumsi.
Hosam Aly
2
@ John: Reaksi pertama saya juga tetapi saya salah - lihat pertanyaan Hosams untuk detailnya.
Henk Holterman
38
Array multi-dimensi seharusnya secara logis lebih efisien tetapi implementasinya oleh kompiler JIT tidak. Kode di atas tidak berguna karena tidak menunjukkan akses array dalam satu lingkaran.
ILoveFortran
3
@Henk Holterman - Lihat jawaban saya di bawah ini, Mungkin saja bahwa pada windows array bergerigi cepat tetapi kita harus menyadari bahwa ini sepenuhnya CLR spesifik dan bukan halnya dengan misalnya mono ...
John Leidegren
12
Saya tahu ini adalah pertanyaan lama, hanya ingin tahu apakah CLR telah dioptimalkan untuk array multidimensi sejak pertanyaan ini diajukan.
Anthony Nichols
197

Array multidimensi menciptakan tata letak memori linier yang bagus sementara array bergerigi menyiratkan beberapa tingkat tipuan tambahan.

Mencari nilai jagged[3][6]dalam array bergerigivar jagged = new int[10][5] berfungsi seperti ini: Cari elemen pada indeks 3 (yang merupakan array) dan cari elemen pada indeks 6 dalam array itu (yang merupakan nilai). Untuk setiap dimensi dalam kasus ini, ada tampilan tambahan (ini adalah pola akses memori yang mahal).

Array multidimensi diletakkan secara linear dalam memori, nilai aktual ditemukan dengan mengalikan indeks-indeks tersebut. Namun, mengingat array var mult = new int[10,30], Lengthproperti array multidimensi mengembalikan jumlah elemen yaitu 10 * 30 = 300.

The Rankproperti dari array bergerigi selalu 1, tapi array multidimensi dapat memiliki peringkat apapun. The GetLengthMetode array apapun dapat digunakan untuk mendapatkan panjang masing-masing dimensi. Untuk array multidimensi dalam contoh ini mult.GetLength(1)mengembalikan 30.

Mengindeks array multidimensi lebih cepat. misalkan diberi array multidimensi dalam contoh ini mult[1,7]= 30 * 1 + 7 = 37, dapatkan elemen pada indeks 37. Ini adalah pola akses memori yang lebih baik karena hanya satu lokasi memori yang terlibat, yang merupakan alamat dasar dari array.

Oleh karena itu, array multidimensi mengalokasikan blok memori kontinu, sedangkan array bergerigi tidak harus persegi, misalnya jagged[1].Lengthtidak harus sama denganjagged[2].Length , yang akan berlaku untuk array multidimensi apa pun.

Performa

Dari segi kinerja, array multidimensi harus lebih cepat. Jauh lebih cepat, tetapi karena implementasi CLR yang sangat buruk mereka tidak.

 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 

Baris pertama adalah pengaturan array bergerigi, baris kedua menunjukkan array multidimensi dan baris ketiga, yah begitulah seharusnya. Program ditunjukkan di bawah ini, FYI ini diuji menjalankan mono. (Pengaturan waktu windows sangat berbeda, kebanyakan karena variasi implementasi CLR).

Di windows, timing array bergerigi sangat unggul, hampir sama dengan interpretasi saya sendiri tentang seperti apa seharusnya array multidimensi, lihat 'Tunggal ()'. Sayangnya windows JIT-compiler benar-benar bodoh, dan ini sayangnya membuat diskusi kinerja ini sulit, ada terlalu banyak ketidakkonsistenan.

Ini adalah timing yang saya dapatkan di windows, kesepakatan yang sama di sini, baris pertama adalah array bergerigi, multidimensi kedua dan ketiga implementasi multidimensi saya sendiri, perhatikan berapa banyak ini lebih lambat di windows dibandingkan dengan 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

Kode sumber:

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();
    }
}
John Leidegren
sumber
2
Coba atur sendiri waktunya, dan lihat bagaimana keduanya bekerja. Array bergerigi jauh lebih dioptimalkan dalam .NET. Ini mungkin terkait dengan pemeriksaan batas, tetapi terlepas dari alasannya, timing dan tolok ukur dengan jelas menunjukkan bahwa array bergerigi lebih cepat diakses daripada yang multi-dimensi.
Hosam Aly
10
Tetapi timing Anda tampaknya terlalu kecil (beberapa milidetik). Pada level ini Anda akan mengalami banyak gangguan dari layanan sistem dan / atau driver. Buat tes Anda jauh lebih besar, setidaknya satu atau dua detik.
Hosam Aly
8
@JohnLeidegren: Fakta bahwa array multi-dimensi bekerja lebih baik ketika mengindeks satu dimensi daripada yang lain telah dipahami selama setengah abad, karena elemen-elemen yang berbeda hanya dalam satu dimensi tertentu akan disimpan secara berurutan dalam memori, dan dengan banyak jenis memori (dulu dan sekarang), mengakses item berurutan lebih cepat daripada mengakses item yang jauh. Saya pikir dalam. Net seseorang harus mendapatkan hasil pengindeksan yang optimal dengan subskrip terakhir yang Anda lakukan, tetapi menguji waktu dengan subskrip yang ditukar mungkin informatif dalam hal apa pun.
supercat
16
@supercat: array multi-dimensi dalam C # disimpan dalam urutan baris-utama , menukar urutan langganan akan lebih lambat karena Anda akan mengakses memori secara non-berurutan. BTW kali yang dilaporkan tidak lagi akurat, saya mendapatkan waktu hampir dua kali lebih cepat untuk array multi-dimensi daripada array bergerigi (diuji pada. CLR NET terbaru), yang adalah bagaimana seharusnya ..
Amro
9
Saya tahu ini agak aneh, tetapi saya harus menyebutkan bahwa ini bukan Windows vs Mono, tetapi CLR vs Mono. Anda kadang-kadang tampaknya membingungkan mereka. Keduanya tidak setara; Mono juga berfungsi di Windows.
Magus
70

Sederhananya array multidimensi mirip dengan tabel di DBMS.
Array of Array (jagged array) memungkinkan Anda membuat setiap elemen memiliki array lain dengan jenis panjang variabel yang sama.

Jadi, jika Anda yakin bahwa struktur data terlihat seperti tabel (baris tetap / kolom), Anda dapat menggunakan array multi dimensi. Array bergerigi adalah elemen tetap & setiap elemen dapat memiliki array dengan panjang variabel

Misalnya Psuedocode:

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

Pikirkan hal di atas sebagai tabel 2x2:

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

Pikirkan hal di atas karena setiap baris memiliki jumlah variabel kolom:

 1 |  2 |  3 | 4
11 | 12
21 | 22 | 23
shahkalpesh
sumber
4
inilah yang benar-benar penting ketika memutuskan apa yang akan digunakan .. bukan kecepatan ini ... kecepatan juga bisa menjadi faktor ketika Anda memiliki array persegi.
Xaser
46

Pendahuluan: Komentar ini ditujukan untuk menjawab jawaban yang diberikan oleh okutane , tetapi karena sistem reputasi konyol SO, saya tidak dapat memposting di tempat yang seharusnya.

Pernyataan Anda bahwa yang satu lebih lambat daripada yang lain karena panggilan metode tidak benar. Satu lebih lambat dari yang lain karena algoritma pemeriksaan batas yang lebih rumit. Anda dapat dengan mudah memverifikasi ini dengan melihat, bukan pada IL, tetapi pada majelis yang dikompilasi. Sebagai contoh, pada 4,5 instal saya, mengakses elemen (melalui pointer di edx) disimpan dalam array dua dimensi yang ditunjukkan oleh ecx dengan indeks yang disimpan dalam eax dan edx terlihat seperti ini:

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]

Di sini, Anda dapat melihat bahwa tidak ada overhead dari panggilan metode. Pengecekan batas sangat berbelit-belit berkat kemungkinan indeks yang tidak nol, yang merupakan fungsi yang tidak ditawarkan dengan array bergerigi. Jika kita menghapus sub, cmp, dan jmps untuk case-case yang bukan nol, kode ini cukup banyak untuk dipecahkan (x*y_max+y)*sizeof(ptr)+sizeof(array_header). Penghitungan ini kira-kira sama cepat (satu kali lipat dapat diganti dengan shift, karena itulah alasan utama kami memilih byte untuk diukur sebagai kekuatan dua bit) seperti yang lainnya untuk akses acak ke suatu elemen.

Komplikasi lain adalah bahwa ada banyak kasus di mana kompiler modern akan mengoptimalkan jauh memeriksa batas-batas untuk akses elemen sambil iterasi melalui array satu dimensi. Hasilnya adalah kode yang pada dasarnya hanya memajukan penunjuk indeks ke memori yang berdekatan dari array. Iterasi naif atas array multi-dimensi umumnya melibatkan lapisan tambahan dari logika bersarang, sehingga kompiler cenderung mengoptimalkan operasi. Jadi, meskipun overhead-memeriksa batas mengakses elemen tunggal diamortisasi ke runtime konstan sehubungan dengan dimensi dan ukuran array, kasus uji sederhana untuk mengukur perbedaan mungkin membutuhkan waktu beberapa kali lebih lama untuk dieksekusi.

Eglin
sumber
1
Terima kasih telah mengoreksi jawaban okutane (bukan Dmitry). Sangat menjengkelkan bahwa orang memberikan jawaban yang salah di Stackoverflow dan mendapatkan 250 suara sementara yang lain memberikan jawaban yang benar dan mendapatkan jauh lebih sedikit. Tetapi pada akhirnya kode IL tidak relevan. Anda harus benar-benar MENGUKUR kecepatan untuk mengatakan apa pun tentang kinerja. Apakah kamu melakukan itu? Saya pikir perbedaannya akan konyol.
Elmue
38

Saya ingin memperbarui ini, karena dalam . Core Core multi-dimensi array lebih cepat daripada array bergerigi . Saya menjalankan tes dari John Leidegren dan ini adalah hasil pada .NET Core 2.0 preview 2. Saya meningkatkan nilai dimensi untuk membuat pengaruh yang mungkin dari aplikasi latar belakang kurang terlihat.

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 

Saya melihat ke pembongkaran dan ini adalah apa yang saya temukan

jagged[i][j][k] = i * j * k; dibutuhkan 34 instruksi untuk dieksekusi

multi[i, j, k] = i * j * k; diperlukan 11 instruksi untuk dieksekusi

single[i * dim * dim + j * dim + k] = i * j * k; dibutuhkan 23 instruksi untuk dieksekusi

Saya tidak dapat mengidentifikasi mengapa array satu dimensi masih lebih cepat daripada multi-dimensi tetapi dugaan saya adalah bahwa itu ada hubungannya dengan beberapa optimasi yang dilakukan pada CPU.

adsamcik
sumber
14

Array multi-dimensi adalah (n-1) matriks -dimensi.

Begitu int[,] square = new int[2,2]juga matriks persegi 2x2, int[,,] cube = new int [3,3,3]adalah matriks kubus - persegi 3x3. Tidak perlu proporsionalitas.

Array bergerigi hanya array array - array di mana setiap sel berisi array.

Jadi MDA proporsional, JD mungkin tidak! Setiap sel dapat berisi array dengan panjang sewenang-wenang!

abatishchev
sumber
7

Ini mungkin telah disebutkan dalam jawaban di atas tetapi tidak secara eksplisit: dengan jagged array yang dapat Anda gunakan array[row]untuk merujuk seluruh baris data, tetapi ini tidak diperbolehkan untuk array multi-d.

lznt
sumber
4

Selain jawaban lain, perhatikan bahwa array multidimensi dialokasikan sebagai satu objek tebal besar di heap. Ini memiliki beberapa implikasi:

  1. Beberapa array multidimensi akan dialokasikan pada Large Object Heap (LOH) di mana rekan-rekan array bergerigi ekivalen mereka seharusnya tidak memilikinya.
  2. GC akan perlu menemukan satu blok bebas memori yang berdekatan untuk mengalokasikan array multidimensi, sedangkan array yang bergerigi mungkin dapat mengisi celah yang disebabkan oleh tumpukan heal ... ini biasanya bukan masalah di .NET karena pemadatan , tetapi LOH tidak dipadatkan secara default (Anda harus memintanya, dan Anda harus bertanya setiap kali Anda menginginkannya).
  3. Anda akan ingin mencari cara<gcAllowVeryLargeObjects> array multidimensi sebelum masalah akan pernah muncul jika Anda hanya menggunakan array bergerigi.
Joe Amenta
sumber
2

Saya mem-parsing file .il yang dihasilkan oleh ildasm untuk membangun database assemnblies, kelas, metode, dan prosedur tersimpan untuk digunakan melakukan konversi. Saya menemukan yang berikut, yang mematahkan parsing saya.

.method private hidebysig instance uint32[0...,0...] 
        GenerateWorkingKey(uint8[] key,
                           bool forEncryption) cil managed

Buku Expert .NET 2.0 IL Assembler, oleh Serge Lidin, Apress, yang diterbitkan tahun 2006, Bab 8, Primitive Types and Signatures, hlm. 149-150 menjelaskan.

<type>[]disebut sebagai Vektor dari <type>,

<type>[<bounds> [<bounds>**] ] disebut array <type>

**berarti dapat diulang, [ ]berarti opsional.

Contoh: Biarkan <type> = int32.

1) int32[...,...]adalah susunan dua dimensi dari batas bawah dan ukuran yang tidak ditentukan

2) int32[2...5]adalah array satu dimensi dari batas bawah 2 dan ukuran 4.

3) int32[0...,0...]adalah susunan dua dimensi dengan batas bawah 0 dan ukuran tidak terdefinisi.

Tom

Thomas C Hutchinson
sumber