Çok boyutlu bir dizi ile C # dizileri arasındaki farklar nelerdir?


454

C # 'daki çok boyutlu diziler double[,]ve dizi dizileri arasındaki farklar nelerdir double[][]?

Bir fark varsa, her biri için en iyi kullanım nedir?


7
Birincisi double[,]dikdörtgen dizidir, double[][]"pürüzlü dizi" olarak bilinir. Birincisi her satır için aynı sayıda "sütuna" sahip olurken, ikincisi (potansiyel olarak) her satır için farklı sayıda "sütuna" sahip olacaktır.
GreatAndPowerfulOz

Yanıtlar:


334

Diziler dizisi (tırtıklı diziler) çok boyutlu dizilerden daha hızlıdır ve daha etkin kullanılabilir. Çok boyutlu diziler daha iyi sözdizimine sahiptir.

Pürüzlü ve çok boyutlu diziler kullanarak basit bir kod yazarsanız ve ardından derlenmiş montajı bir IL sökücü ile incelerseniz, pürüzlü (veya tek boyutlu) dizilerden depolama ve almanın basit IL talimatları olduğunu görürsünüz; çok boyutlu diziler için aynı işlemler yöntemdir her zaman yavaş olan istilalar.

Aşağıdaki yöntemleri düşünün:

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'leri şu şekilde olacaktır:

.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

Pürüzlü diziler kullanırken, satır değiştirme ve satır yeniden boyutlandırma gibi işlemleri kolayca gerçekleştirebilirsiniz. Belki bazı durumlarda çok boyutlu dizilerin kullanımı daha güvenli olacaktır, ancak Microsoft FxCop bile projelerinizi analiz etmek için kullandığınızda çok boyutlu yerine pürüzlü dizilerin kullanılması gerektiğini söyler.


7
@ John, onları kendin ölç ve varsayımlar yapma.
Hosam Aly

2
@John: Ben de ilk tepkim ama yanılmışım - ayrıntılar için Hosams sorusuna bakın.
Henk Holterman

38
Çok boyutlu diziler mantıksal olarak daha etkili olmalıdır, ancak bunların JIT derleyicisi tarafından uygulanması etkili değildir. Yukarıdaki kod bir döngüde dizi erişimi göstermediği için yararlı değildir.
ILoveFortran

3
@Henk Holterman - Aşağıdaki cevabımı görün, Pencerelerde pürüzlü dizilerin hızlı olması, ancak bunun tamamen
CLR'ye

12
Bunun eski bir soru olduğunu biliyorum, sadece bu soru sorulduğundan beri CLR'nin çok boyutlu diziler için optimize edilip edilmediğini merak ediyorum.
Anthony Nichols

197

Çok boyutlu bir dizi, hoş bir doğrusal bellek düzeni yaratırken, tırtıklı bir dizi, fazladan birkaç ilave dolaylama seviyesi anlamına gelir.

jagged[3][6]Pürüzlü bir dizideki değeri aramak şu şekilde var jagged = new int[10][5]çalışır: Dizin 3'teki (bir dizi olan) öğeye bakın ve o dizideki (bir değer olan) dizin 6'daki öğeye bakın. Bu durumda her boyut için ek bir arama var (bu pahalı bir bellek erişim modelidir).

Çok boyutlu bir dizi, bellekte doğrusal olarak düzenlenir, gerçek değer, dizinleri çarparak bulunur. Bununla birlikte, bir dizi verilen var mult = new int[10,30], Lengthelemanlar 10 x 30 = 300, yani bu boyutlu dizinin özelliği toplam sayısını verir.

RankPürüzlü dizinin özelliği her zaman 1'dir, ancak çok boyutlu bir dizi herhangi bir dereceye sahip olabilir. GetLengthHerhangi bir dizi yöntem, her boyutun uzunluğu elde etmek için kullanılabilir. Bu örnekteki çok boyutlu dizi için mult.GetLength(1)30 döndürür.

Çok boyutlu dizinin indekslenmesi daha hızlıdır. örneğin bu örnekteki çok boyutlu dizi verildiğinde mult[1,7]= 30 * 1 + 7 = 37, bu dizindeki öğeyi 37 alır. Bu daha iyi bir bellek erişim modelidir, çünkü dizinin temel adresi olan yalnızca bir bellek konumu söz konusudur.

Bu nedenle, çok boyutlu bir dizi sürekli bir bellek bloğu tahsis ederken, pürüzlü bir dizinin kare jagged[1].Lengtholması gerekmez , örneğin eşit olması gerekmez jagged[2].Length, bu da herhangi bir çok boyutlu dizi için geçerli olacaktır.

Verim

Performans açısından, çok boyutlu diziler daha hızlı olmalıdır. Çok daha hızlı, ancak gerçekten kötü bir CLR uygulaması nedeniyle değiller.

 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 

İlk sıra tırtıklı dizilerin zamanlamaları, ikincisi çok boyutlu dizileri ve üçüncüsü de böyle olması gerektiğini gösteriyor. Program aşağıda gösterilmiştir, FYI bu mono çalışarak test edilmiştir. (Windows zamanlamaları, çoğunlukla CLR uygulama varyasyonları nedeniyle büyük ölçüde farklıdır).

Pencerelerde, pürüzlü dizilerin zamanlamaları büyük ölçüde üstündür, neredeyse çok boyutlu dizinin nasıl görünmesi gerektiğine dair kendi yorumumla aynı, bkz. 'Tek ()'. Ne yazık ki Windows JIT-derleyicisi gerçekten aptalca ve bu ne yazık ki bu performans tartışmalarını zorlaştırıyor, çok fazla tutarsızlık var.

Bunlar pencerelerde aldığım zamanlamalar, burada aynı anlaşma, ilk sıra tırtıklı diziler, ikinci çok boyutlu ve üçüncü kendi çok boyutlu uygulamam, bu pencerelerde mono ile karşılaştırıldığında ne kadar yavaş olduğunu not edin.

  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

Kaynak kodu:

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

2
Onları kendiniz zamanlamayı deneyin ve her ikisinin de nasıl performans gösterdiğini görün. Pürüzlü diziler .NET'te çok daha optimize edilmiştir. Sınırların kontrolü ile ilgili olabilir, ancak nedenine bakılmaksızın, zamanlamalar ve karşılaştırmalar, pürüzlü dizilerin çok boyutlu olanlardan daha hızlı erişildiğini açıkça gösterir.
Hosam Aly

10
Ancak zamanlamalarınız çok küçük görünüyor (birkaç milisaniye). Bu seviyede sistem hizmetlerinden ve / veya sürücülerden çok fazla parazitleneceksiniz. Testlerinizi çok daha büyük yapın, en azından bir iki saniye sürün.
Hosam Aly

8
@JohnLeidegren: Çok boyutlu dizilerin bir boyutu diğerinden indekslerken daha iyi çalışması gerçeği yarım yüzyıl boyunca anlaşılmıştır, çünkü sadece belirli bir boyutta farklılık gösteren elemanlar ardışık olarak bellekte ve birçok bellekte (geçmişte) saklanacaktır ve mevcut), ardışık öğelere erişmek, uzak öğelere erişmekten daha hızlıdır. Ben .net biri yaptığınız olan son alt simge ile en iyi sonuç indeksleme almak gerektiğini düşünüyorum, ama takas abonelikleri ile zaman test her durumda bilgilendirici olabilir.
supercat

16
@supercat: C # 'daki çok boyutlu diziler sıraya göre sıralı olarak saklanır, ardışık olarak belleğe eriştiğiniz için abonelerin sırasını değiştirmek daha yavaş olur. BTW bildirilen süreler artık doğru değil, çok boyutlu diziler için pürüzlü dizilerden (en son .NET CLR'de test edildi) neredeyse iki kat daha hızlı süreler alıyorum.
Amro

9
Bunun biraz bilgiç olduğunu biliyorum, ama bunun Windows'a karşı Mono değil CLR'ye karşı Mono olduğunu belirtmeliyim. Bazen bunları karıştırıyorsunuz. İkisi eşdeğer değildir; Mono Windows üzerinde de çalışır.
Magus

70

Basitçe söylemek gerekirse çok boyutlu diziler DBMS'deki bir tabloya benzer.
Array of Array (tırtıklı dizi), her öğenin aynı türde değişken uzunlukta başka bir dizi tutmasını sağlar.

Dolayısıyla, veri yapısının bir tabloya benzediğinden eminseniz (sabit satırlar / sütunlar), çok boyutlu bir dizi kullanabilirsiniz. Pürüzlü dizi sabit öğelerdir ve her öğe değişken uzunlukta bir dizi tutabilir

Örnek: Psuedocode:

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

Yukarıdakileri 2x2 tablo olarak düşünün:

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

Yukarıdakileri, her satırda değişken sayıda sütun bulunduğunu düşünün:

 1 |  2 |  3 | 4
11 | 12
21 | 22 | 23

4
ne kullanmak için karar verirken gerçekten önemli olan budur .. bu hız şey değil .. kare hızı olduğunda iyi bir hız bir faktör olabilir.
Xaser

46

Önsöz: Bu yorum okutan tarafından verilen cevabı ele almayı amaçlamaktadır , ancak SO'nun aptal itibar sistemi nedeniyle, ait olduğu yere gönderemiyorum.

Yöntem çağrıları nedeniyle birinin diğerinden daha yavaş olduğu iddianız doğru değil. Daha karmaşık sınır kontrol algoritmaları nedeniyle biri diğerinden daha yavaştır. IL'ye değil, derlenmiş montaja bakarak bunu kolayca doğrulayabilirsiniz. Örneğin, 4.5 kurulumumda, eax ve edx'te saklanan dizinlerle ecx tarafından işaret edilen iki boyutlu bir dizide saklanan bir öğeye (edx'te işaretçi aracılığıyla) erişmek şöyle görünür:

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]

Burada, yöntem çağrılarından herhangi bir ek yük olmadığını görebilirsiniz. Sınır kontrolü, pürüzlü dizilerle sunulmayan bir işlevsellik olan sıfır olmayan dizinlerin olasılığı sayesinde çok kıvrımlıdır. Sıfır olmayan durumlar için sub, cmp ve jmp'leri kaldırırsak, kod hemen hemen çözülür (x*y_max+y)*sizeof(ptr)+sizeof(array_header). Bu hesaplama, bir öğeye rastgele erişim için başka herhangi bir şey kadar hızlıdır (bir çarpma bir kaydırma ile değiştirilebilir, çünkü baytların iki bitlik güç olarak boyutlandırılmasını seçmemizin nedeni budur).

Bir başka komplikasyon, modern bir derleyicinin, tek boyutlu bir dizi üzerinde yineleme yaparken, eleman erişimi için iç içe sınır kontrolünü optimize edeceği birçok durum olmasıdır. Sonuç, temelde dizinin bitişik belleği üzerinde bir indeks işaretçisini ilerleten koddur. Çok boyutlu diziler üzerinde saf yineleme genellikle fazladan iç içe mantık tabakası içerir, bu nedenle bir derleyicinin işlemi optimize etme olasılığı daha düşüktür. Bu nedenle, tek bir öğeye erişmenin sınır denetleme yükü, dizi boyutları ve boyutlarına göre sabit çalışma süresine amortismana rağmen, farkı ölçmek için basit bir test durumunun yürütülmesi çok daha uzun sürebilir.


1
Okutan'ın cevabını düzelttiğiniz için teşekkürler (Dmitry değil). Başkalarının doğru cevaplar vermesi ve çok daha az alması, insanların Stackoverflow üzerinde yanlış cevaplar vermesi ve 250 oy alması can sıkıcı bir durum. Ancak sonunda IL kodu önemsizdir. Performansla ilgili herhangi bir şey söyleme hızını gerçekten ölçmelisiniz. Onu yaptınmı? Farkın saçma olacağını düşünüyorum.
Elmue

38

Bu konuda güncellemek istiyorum, çünkü .NET Core'da çok boyutlu diziler pürüzlü dizilerden daha hızlı . John Leidegren'in testlerini yaptım ve bunlar .NET Core 2.0 önizleme 2'deki sonuçlar. Arka plan uygulamalarından olası etkileri daha az görünür hale getirmek için boyut değerini artırdım .

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 

Demontajlara baktım ve bulduğum şey bu

jagged[i][j][k] = i * j * k; yürütmek için 34 talimat gerekiyor

multi[i, j, k] = i * j * k; yürütmek için 11 talimat gerekiyor

single[i * dim * dim + j * dim + k] = i * j * k; yürütmek için 23 talimat gerekiyor

Tek boyutlu dizilerin neden hala çok boyutludan daha hızlı olduğunu belirleyemedim, ancak tahminim CPU'da yapılan bazı optimizasyonlarla ilgili olması gerektiği


14

Çok boyutlu diziler (n-1) boyutlu matrislerdir.

Yani int[,] square = new int[2,2]kare matris 2x2, int[,,] cube = new int [3,3,3]bir küp - kare matris 3x3. Orantılılık gerekli değildir.

Pürüzlü diziler sadece diziler dizisidir - her hücrenin bir dizi içerdiği bir dizi.

Yani MDA orantılıdır, JD olmayabilir! Her hücre bir dizi keyfi uzunluk içerebilir!


7

Bu, yukarıdaki cevaplarda belirtilmiş olabilir, ancak açıkça değil: pürüzlü dizi ile array[row]tüm veri satırına başvurmak için kullanabilirsiniz , ancak çoklu d dizileri için buna izin verilmez.


4

Diğer cevaplara ek olarak, çok boyutlu bir dizinin yığın üzerinde büyük bir tıknaz nesne olarak tahsis edildiğine dikkat edin. Bunun bazı sonuçları vardır:

  1. Bazı çok boyutlu diziler, eşdeğer pürüzlü dizi meslektaşlarının aksi takdirde sahip olmayacağı Büyük Nesne Yığını'na (LOH) tahsis edilecektir.
  2. GC'nin çok boyutlu bir dizi ayırmak için tek bir bitişik boş bellek bloğu bulması gerekirken, pürüzlü bir dizi yığın parçalanmasının neden olduğu boşlukları doldurabilir ... bu sıkıştırma nedeniyle genellikle .NET'te bir sorun değildir. , ancak LOH varsayılan olarak sıkıştırılmaz (istemeniz gerekir ve her istediğinizde sormanız gerekir).
  3. Sen içine bakmak isteyeceksiniz <gcAllowVeryLargeObjects>çok boyutlu diziler için yol sadece hiç pürüzlü diziler kullanırsanız sorun yetişen en önce.

2

Bir dönüşüm yapmak için assemnblies, sınıflar, yöntemler ve saklı yordamlar veritabanı oluşturmak için ildasm tarafından oluşturulan .il dosyalarını ayrıştırıyorum. Aşağıdakilere rastladım, bu da ayrışmamı kırdı.

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

2006. Bölüm, İlkel Türler ve İmzalar, Serge Lidin, Uzman .NET 2.0 IL Assembler adlı kitap, İlkel Türler ve İmzalar, s. 149-150 açıklar.

<type>[]bir Vektör olarak adlandırılır <type>,

<type>[<bounds> [<bounds>**] ] dizisi olarak adlandırılır <type>

**ortalamalar tekrarlanabilir, [ ]ortalamalar isteğe bağlıdır.

Örnekler: Let <type> = int32.

1) int32[...,...]iki boyutlu tanımlanmamış alt sınırlar ve boyutlar dizisidir

2) int32[2...5]alt sınır 2 ve boyut 4'ün tek boyutlu bir dizisidir.

3) int32[0...,0...]alt sınır 0 ve tanımlanmamış boyuttan iki boyutlu bir dizidir.

Tom

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.