web-dev-qa-db-fra.com

Quelles sont les différences entre un tableau multidimensionnel et un tableau de tableaux en C #?

Quelles sont les différences entre les tableaux multidimensionnels double[,] et les tableaux de tableaux double[][] en C #?

S'il y a une différence, quelle est la meilleure utilisation pour chacun?

428
ecleel

Les tableaux de tableaux (tableaux dentelés) sont plus rapides que les tableaux multidimensionnels et peuvent être utilisés plus efficacement. Les tableaux multidimensionnels ont une syntaxe plus agréable.

Si vous écrivez du code simple à l'aide de tableaux multidimensionnels et déchiquetés, puis inspectez l'assembly compilé à l'aide d'un désassembleur IL, vous constaterez que le stockage et la récupération à partir de tableaux déchiquetés (ou à une dimension) sont de simples instructions IL, tandis que les mêmes opérations pour les tableaux multidimensionnels sont effectuées. invocations qui sont toujours plus lentes.

Considérez les méthodes suivantes:

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

Leur IL sera le suivant:

.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

Lorsque vous utilisez des tableaux dentelés, vous pouvez facilement effectuer des opérations telles que l’échange et le redimensionnement de lignes. Peut-être que dans certains cas, l’utilisation de tableaux multidimensionnels sera plus sûre, mais même Microsoft FxCop indique que les tableaux déchiquetés doivent être utilisés au lieu de multidimensionnels lorsque vous l’utilisez pour analyser vos projets.

314
okutane

Un tableau multidimensionnel crée une structure de mémoire linéaire Nice alors qu'un tableau en dents de scie implique plusieurs niveaux d'indirection supplémentaires.

La recherche de la valeur jagged[3][6] dans un tableau irrégulier var jagged = new int[10][5] fonctionne comme suit: Recherchez l'élément à l'index 3 (qui est un tableau) et recherchez l'élément à l'index 6 dans ce tableau (qui est un valeur). Pour chaque dimension dans ce cas, il existe une recherche supplémentaire (il s'agit d'un modèle d'accès mémoire coûteux).

Un tableau multidimensionnel est structuré de manière linéaire en mémoire, la valeur réelle est trouvée en multipliant les index. Cependant, étant donné le tableau var mult = new int[10,30], la propriété Length de ce tableau multidimensionnel renvoie le nombre total d’éléments, à savoir 10 * 30 = 300.

La propriété Rank d'un tableau en escalier est toujours égale à 1, mais un tableau multidimensionnel peut avoir n'importe quel rang. La méthode GetLength de n'importe quel tableau peut être utilisée pour obtenir la longueur de chaque dimension. Dans cet exemple, le tableau multidimensionnel mult.GetLength(1) renvoie 30.

L'indexation du tableau multidimensionnel est plus rapide. par exemple. étant donné le tableau multidimensionnel de cet exemple mult[1,7] = 30 * 1 + 7 = 37, obtenez l'élément à cet index 37. Il s'agit d'un meilleur modèle d'accès à la mémoire car un seul emplacement de mémoire est impliqué, qui est l'adresse de base de le tableau.

Un tableau multidimensionnel alloue donc un bloc de mémoire continu, alors qu'un tableau en dents de scie n'a pas besoin d'être carré, par ex. jagged[1].Length ne doit pas nécessairement être égal à jagged[2].Length, ce qui serait vrai pour tout tableau multidimensionnel.

Performance

En termes de performances, les tableaux multidimensionnels devraient être plus rapides. Beaucoup plus rapidement, mais à cause d'une très mauvaise implémentation CLR, ils ne le sont pas.

 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 première ligne contient les timings des tableaux en dents de scie, la seconde affiche les tableaux multidimensionnels et la troisième, c'est bien ainsi. Le programme est présenté ci-dessous, pour votre information, cela a été testé en mode mono. (Les horaires de Windows sont très différents, principalement en raison des variations de la mise en œuvre du CLR).

Sur les fenêtres, les timings des tableaux déchiquetés sont nettement supérieurs, à peu près identiques à ceux de ma propre interprétation de ce à quoi devrait ressembler un tableau multidimensionnel, voir 'Single ()'. Malheureusement, le compilateur JIT de Windows est vraiment stupide, et cela rend malheureusement ces discussions sur les performances difficiles, car il y a trop d'incohérences.

Ce sont les timings que j'ai sur les fenêtres, même affaire ici, la première rangée sont des tableaux déchiquetés, la deuxième multidimensionnelle et la troisième ma propre implémentation de multidimensionnelle, notez combien cela est plus lent sur les fenêtres par rapport à 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

Code source:

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

Les tableaux multidimensionnels sont simplement similaires à une table dans un SGBD.
Tableau de tableau (tableau irrégulier) permet à chaque élément de contenir un autre tableau du même type de longueur variable.

Ainsi, si vous êtes certain que la structure des données ressemble à une table (lignes/colonnes fixes), vous pouvez utiliser un tableau multidimensionnel. Les tableaux en dents de scie sont des éléments fixes et chaque élément peut contenir un tableau de longueur variable

Par exemple. Psuedocode:

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

Pensez à ce qui précède comme une table 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 }; 

Pensez à ce qui précède comme chaque ligne ayant un nombre variable de colonnes:

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

Préface: Ce commentaire est destiné à adresser la réponse fournie par okutane , mais à cause du système de réputation stupide de SO, je ne peux pas postez-le où il appartient.

Votre affirmation selon laquelle l'un est plus lent que l'autre à cause des appels de méthode n'est pas correcte. L’un est plus lent que l’autre en raison de la complexité des algorithmes de vérification des limites. Vous pouvez facilement le vérifier en regardant non pas l’IL, mais l’Assemblée compilée. Par exemple, sur mon installation 4.5, accéder à un élément (via un pointeur dans edx) stocké dans un tableau à deux dimensions pointé par ecx avec des index stockés dans eax et edx ressemble à ceci:

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]

Ici, vous pouvez voir qu’il n’ya pas de surcharge liée aux appels de méthode. La vérification des limites est juste très compliquée grâce à la possibilité d’index non nuls, une fonctionnalité qui n’est pas proposée avec les tableaux déchiquetés. Si nous supprimons les sous, cmp et jmps pour les cas non nuls, le code se résorbera plutôt en (x*y_max+y)*sizeof(ptr)+sizeof(array_header). Ce calcul est à peu près aussi rapide (une multiplication pourrait être remplacée par un décalage, car c’est la raison pour laquelle nous avons choisi la taille en octets d'une puissance de deux bits), comme toute autre chose pour un accès aléatoire à un élément.

Une autre complication réside dans le fait qu’il existe de nombreux cas dans lesquels un compilateur moderne optimisera la vérification des limites imbriquées pour l’accès aux éléments tout en effectuant une itération sur un tableau à une dimension. Le résultat est un code qui avance simplement un pointeur d'index sur la mémoire contiguë du tableau. L'itération naïve sur des tableaux multidimensionnels implique généralement une couche supplémentaire de logique imbriquée, de sorte qu'un compilateur est moins susceptible d'optimiser l'opération. Ainsi, même si la surcharge liée à la vérification des limites d'accès à un seul élément est amortie par une durée d'exécution constante en ce qui concerne les dimensions et la taille des tableaux, l'exécution d'un test simple pour mesurer la différence peut prendre beaucoup plus de temps.

39
Eglin

Je voudrais mettre à jour à ce sujet, car dans . Les tableaux multidimensionnels .NET Core sont plus rapides que les tableaux à dents de scie . J'ai exécuté les tests de John Leidegren et voici les résultats obtenus sur .NET Core 2.0 preview 2. J'ai augmenté la valeur de la dimension afin de rendre moins visibles les influences éventuelles des applications en arrière-plan.

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 

J'ai examiné les désassemblages et voici ce que j'ai trouvé

jagged[i][j][k] = i * j * k; avait besoin de 34 instructions pour s'exécuter

multi[i, j, k] = i * j * k; avait besoin de 11 instructions pour s'exécuter

single[i * dim * dim + j * dim + k] = i * j * k; avait besoin de 23 instructions pour s'exécuter

Je n'ai pas pu identifier pourquoi les tableaux unidimensionnels étaient encore plus rapides que les tableaux multidimensionnels, mais je suppose que cela est dû à une optimisation réalisée sur le processeur.

28
adsamcik

Les tableaux multidimensionnels sont des matrices de dimensions (n-1).

Donc, int[,] square = new int[2,2] est une matrice carrée 2x2, int[,,] cube = new int [3,3,3] est une matrice cube-carré 3x3. La proportionnalité n'est pas requise.

Les tableaux en dents de scie ne sont qu'un tableau de tableaux - un tableau où chaque cellule contient un tableau.

Donc, les MDA sont proportionnelles, JD n'est peut-être pas! Chaque cellule peut contenir un tableau de longueur arbitraire!

14
abatishchev

Cela a peut-être été mentionné dans les réponses ci-dessus, mais pas explicitement: avec un tableau irrégulier, vous pouvez utiliser array[row] pour faire référence à une ligne entière de données, mais ceci n'est pas autorisé pour les tableaux multi-d.

7
lznt

En plus des autres réponses, notez qu'un tableau multidimensionnel est alloué en tant qu'objet volumineux sur le tas. Cela a des implications:

  1. Certains tableaux multidimensionnels seront alloués sur le tas d'objets volumineux (LOH) où leurs équivalents de tableaux en dents de scie équivalents n'auraient pas autrement.
  2. Le GC devra trouver un seul bloc de mémoire libre contigu pour allouer un tableau multidimensionnel, alors qu'un tableau irrégulier pourrait permettre de combler les vides causés par la fragmentation du tas ... ce n'est généralement pas un problème dans .NET en raison du compactage. , mais le LOH ne se compacte pas par défaut (vous devez le demander et vous devez le demander chaque fois que vous le souhaitez).
  3. Vous aurez envie de regarder <gcAllowVeryLargeObjects> pour les tableaux multidimensionnels avant que le problème ne se pose si vous utilisez uniquement des tableaux déchiquetés.
2
Joe Amenta

J'analyse les fichiers .il générés par ildasm pour créer une base de données d'assemmblies, de classes, de méthodes et de procédures stockées à utiliser pour effectuer une conversion. Je suis tombé sur ce qui suit, qui a cassé mon analyse.

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

Le livre Expert .NET 2.0 IL Assembler, de Serge Lidin, Apress, publié en 2006, chapitre 8, Types primitifs et signatures, p. 149-150, explique.

<type>[] est appelé un vecteur de <type>,

<type>[<bounds> [<bounds>**] ] est appelé un tableau de <type>

** signifie que peut être répété, [ ] signifie optionnel.

Exemples: Soit <type> = int32.

1) int32[...,...] est un tableau à deux dimensions de limites inférieures non définies et de tailles

2) int32[2...5] est un tableau à une dimension de borne inférieure 2 et de taille 4.

3) int32[0...,0...] est un tableau à deux dimensions de bornes inférieures 0 et de taille indéfinie.

À M

2
Thomas C Hutchinson