Yanıtlar:
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.
Ç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]
, Length
elemanlar 10 x 30 = 300, yani bu boyutlu dizinin özelliği toplam sayısını verir.
Rank
Pürüzlü dizinin özelliği her zaman 1'dir, ancak çok boyutlu bir dizi herhangi bir dereceye sahip olabilir. GetLength
Herhangi 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].Length
olması gerekmez , örneğin eşit olması gerekmez jagged[2].Length
, bu da herhangi bir çok boyutlu dizi için geçerli olacaktır.
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();
}
}
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
Ö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.
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
Ç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!
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:
<gcAllowVeryLargeObjects>
çok boyutlu diziler için yol sadece hiç pürüzlü diziler kullanırsanız sorun yetişen en önce.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
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.