Risposte:
Le matrici di matrici (matrici frastagliate) sono più veloci delle matrici multidimensionali e possono essere utilizzate in modo più efficace. Le matrici multidimensionali hanno una sintassi migliore.
Se si scrive un codice semplice utilizzando matrici frastagliate e multidimensionali e quindi si ispeziona l'assieme compilato con un disassemblatore IL, si vedrà che la memorizzazione e il recupero da matrici frastagliate (o monodimensionali) sono semplici istruzioni IL mentre le stesse operazioni per matrici multidimensionali sono metodo invocazioni che sono sempre più lente.
Considera i seguenti metodi:
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 loro IL sarà il seguente:
.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
Quando si utilizzano array frastagliati, è possibile eseguire facilmente operazioni come lo scambio di righe e il ridimensionamento delle righe. Forse in alcuni casi l'uso di array multidimensionali sarà più sicuro, ma anche Microsoft FxCop dice che gli array frastagliati dovrebbero essere usati anziché multidimensionali quando lo usi per analizzare i tuoi progetti.
Un array multidimensionale crea un piacevole layout di memoria lineare mentre un array frastagliato implica diversi livelli extra di indiretta.
Cercare il valore jagged[3][6]
in un array frastagliato var jagged = new int[10][5]
funziona in questo modo: Cerca l'elemento nell'indice 3 (che è un array) e cerca l'elemento nell'indice 6 in tale array (che è un valore). Per ogni dimensione in questo caso, c'è un'ulteriore ricerca (questo è un modello di accesso alla memoria costoso).
Un array multidimensionale è disposto linearmente in memoria, il valore effettivo si trova moltiplicando gli indici. Tuttavia, dato l'array var mult = new int[10,30]
, la Length
proprietà di quell'array multidimensionale restituisce il numero totale di elementi, ovvero 10 * 30 = 300.
La Rank
proprietà di un array frastagliato è sempre 1, ma un array multidimensionale può avere qualsiasi rango. Il GetLength
metodo di qualsiasi array può essere utilizzato per ottenere la lunghezza di ogni dimensione. Per l'array multidimensionale in questo esempio mult.GetLength(1)
restituisce 30.
L'indicizzazione dell'array multidimensionale è più veloce. ad esempio, dato l'array multidimensionale in questo esempio mult[1,7]
= 30 * 1 + 7 = 37, ottenere l'elemento in quell'indice 37. Questo è un modello di accesso alla memoria migliore perché è coinvolta solo una posizione di memoria, che è l'indirizzo di base dell'array.
Un array multidimensionale alloca quindi un blocco di memoria continuo, mentre un array frastagliato non deve essere quadrato, ad es. jagged[1].Length
Non deve essere uguale jagged[2].Length
, il che sarebbe vero per qualsiasi array multidimensionale.
Le matrici multidimensionali per quanto riguarda le prestazioni dovrebbero essere più veloci. Molto più veloce, ma a causa di una cattiva implementazione del CLR non lo sono.
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
La prima riga indica i tempi delle matrici frastagliate, la seconda mostra le matrici multidimensionali e la terza, ecco come dovrebbe essere. Il programma è mostrato di seguito, FYI questo è stato testato in esecuzione mono. (I tempi di Windows sono molto diversi, principalmente a causa delle variazioni di implementazione CLR).
Su Windows, i tempi delle matrici frastagliate sono notevolmente superiori, più o meno come la mia interpretazione di come dovrebbe essere la matrice multidimensionale, vedi 'Single ()'. Purtroppo il compilatore JIT di Windows è davvero stupido, e questo purtroppo rende difficili queste discussioni sulle prestazioni, ci sono troppe incongruenze.
Questi sono i tempi che ho ottenuto su Windows, lo stesso affare qui, la prima riga sono matrici frastagliate, la seconda multidimensionale e la terza la mia implementazione del multidimensionale, nota quanto questo è più lento su Windows rispetto al 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
Codice sorgente:
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();
}
}
In poche parole, gli array multidimensionali sono simili a una tabella in DBMS.
Array of Array (array frastagliato) consente a ciascun elemento di contenere un altro array dello stesso tipo di lunghezza variabile.
Pertanto, se si è certi che la struttura dei dati assomigli a una tabella (righe / colonne fisse), è possibile utilizzare un array multidimensionale. Gli array frastagliati sono elementi fissi e ogni elemento può contenere un array di lunghezza variabile
Ad esempio Psuedocode:
int[,] data = new int[2,2];
data[0,0] = 1;
data[0,1] = 2;
data[1,0] = 3;
data[1,1] = 4;
Pensa a quanto sopra come una tabella 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 };
Pensa a quanto sopra come ogni riga con un numero variabile di colonne:
1 | 2 | 3 | 4 11 | 12 21 | 22 | 23
Prefazione: questo commento ha lo scopo di rispondere alla risposta fornita da okutane , ma a causa del sistema di reputazione sciocco di SO, non posso pubblicarlo dove appartiene.
La tua affermazione che uno è più lento dell'altro a causa delle chiamate del metodo non è corretta. Uno è più lento dell'altro a causa di algoritmi di controllo dei limiti più complicati. È possibile verificarlo facilmente osservando non IL, ma l'assembly compilato. Ad esempio, sulla mia installazione 4.5, l'accesso a un elemento (tramite puntatore in edx) memorizzato in un array bidimensionale a cui punta ecx con indici memorizzati in eax ed edx è simile al seguente:
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]
Qui puoi vedere che non ci sono spese generali per le chiamate al metodo. Il controllo dei limiti è molto complicato grazie alla possibilità di indici diversi da zero, che è una funzionalità non disponibile con array frastagliati. Se rimuoviamo sub, cmp e jmps per i casi diversi da zero, il codice praticamente si risolve (x*y_max+y)*sizeof(ptr)+sizeof(array_header)
. Questo calcolo è più veloce (una moltiplicazione potrebbe essere sostituita da uno spostamento, poiché questa è l'intera ragione per cui scegliamo byte da dimensionare come potenze di due bit) come qualsiasi altra cosa per l'accesso casuale a un elemento.
Un'altra complicazione è che ci sono molti casi in cui un moderno compilatore ottimizzerà il controllo dei limiti nidificati per l'accesso agli elementi durante l'iterazione su un array a dimensione singola. Il risultato è un codice che sostanzialmente fa avanzare un puntatore indice sulla memoria contigua dell'array. L'iterazione ingenua su array multidimensionali comporta generalmente un ulteriore livello di logica nidificata, quindi un compilatore ha meno probabilità di ottimizzare l'operazione. Quindi, anche se il sovraccarico di controllo dei limiti dell'accesso a un singolo elemento si ammortizza in un runtime costante rispetto alle dimensioni e alle dimensioni dell'array, un semplice test-case per misurare la differenza può richiedere molte volte più tempo per essere eseguito.
Vorrei aggiornarlo, perché in .NET Core gli array multidimensionali sono più veloci degli array frastagliati . Ho eseguito i test di John Leidegren e questi sono i risultati dell'anteprima di .NET Core 2.0 2. Ho aumentato il valore della dimensione per rendere meno visibili eventuali influenze dalle app in background.
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
Ho esaminato gli smontaggi e questo è quello che ho trovato
jagged[i][j][k] = i * j * k;
necessarie 34 istruzioni per l'esecuzione
multi[i, j, k] = i * j * k;
necessarie 11 istruzioni per l'esecuzione
single[i * dim * dim + j * dim + k] = i * j * k;
necessarie 23 istruzioni per l'esecuzione
Non sono riuscito a identificare il motivo per cui gli array monodimensionali erano ancora più veloci di quelli multidimensionali, ma la mia ipotesi è che abbia a che fare con un po 'di ottimizzazione effettuata sulla CPU
Le matrici multidimensionali sono matrici di dimensioni (n-1).
Così int[,] square = new int[2,2]
è matrice quadrata 2x2, int[,,] cube = new int [3,3,3]
è un cubo - matrice quadrata 3x3. La proporzionalità non è richiesta.
Gli array frastagliati sono solo array di array, un array in cui ogni cella contiene un array.
Quindi gli MDA sono proporzionali, JD potrebbe non esserlo! Ogni cella può contenere una matrice di lunghezza arbitraria!
Oltre alle altre risposte, notare che un array multidimensionale è allocato come un grosso grosso oggetto sull'heap. Ciò ha alcune implicazioni:
<gcAllowVeryLargeObjects>
array multidimensionali molto prima che il problema si presenti se usi sempre array di frastagliati.Sto analizzando i file .il generati da ildasm per creare un database di assunzioni, classi, metodi e procedure memorizzate per l'uso durante la conversione. Mi sono imbattuto nel seguente, che ha interrotto la mia analisi.
.method private hidebysig instance uint32[0...,0...]
GenerateWorkingKey(uint8[] key,
bool forEncryption) cil managed
Il libro Expert .NET 2.0 IL Assembler, di Serge Lidin, Apress, pubblicato nel 2006, capitolo 8, Tipi e firme primitivi, pagg. 149-150 spiega.
<type>[]
è definito un vettore di <type>
,
<type>[<bounds> [<bounds>**] ]
è definito un array di <type>
**
i mezzi possono essere ripetuti, i [ ]
mezzi facoltativi.
Esempi: Let <type> = int32
.
1) int32[...,...]
è una matrice bidimensionale di limiti e dimensioni inferiori indefiniti
2) int32[2...5]
è una matrice unidimensionale di limite inferiore 2 e dimensione 4.
3) int32[0...,0...]
è una matrice bidimensionale con limiti inferiori 0 e dimensioni indefinite.
Tom
double[,]
è un array rettangolare, mentredouble[][]
è noto come un "array frastagliato". Il primo avrà lo stesso numero di "colonne" per ogni riga, mentre il secondo avrà (potenzialmente) un numero diverso di "colonne" per ogni riga.