56 votes

Pourquoi les tableaux multidimensionnels dans .NET sont-ils plus lents que les tableaux normaux?

Edit: je m'excuse auprès de tout le monde. J'ai utilisé le terme "tableau en escalier" quand en fait je voulais dire "un tableau multi-dimensionnel" (comme on peut le voir dans mon exemple ci-dessous). Je m'excuse pour l'utilisation du nom incorrect. J'ai effectivement trouvé les tableaux irréguliers pour être plus rapide que multi-dimensionnelle ceux! J'ai ajouté mes mesures pour les tableaux irréguliers.

J'ai essayé d'utiliser un jagged un tableau multi-dimensionnel aujourd'hui, quand j'ai remarqué que c'est la performance n'est pas que je l'aurais attendu. À l'aide d'un simple tableau multidimensionnel et manuellement le calcul des indices a été beaucoup plus rapide (presque deux fois) à l'aide d'un tableau 2D. J'ai écrit un test à l'aide de 1024*1024 tableaux initialisés à des valeurs aléatoires), pour 1000 itérations, et j'ai obtenu les résultats suivants sur ma machine:

sum(double[], int): 2738 ms (100%)
sum(double[,]):     5019 ms (183%)
sum(double[][]):    2540 ms ( 93%)

C'est mon code de test:

public static double sum(double[] d, int l1) {
    // assuming the array is rectangular
    double sum = 0;
    int l2 = d.Length / l1;
    for (int i = 0; i < l1; ++i)
        for (int j = 0; j < l2; ++j)
            sum += d[i * l2 + j];
    return sum;
}

public static double sum(double[,] d) {
    double sum = 0;
    int l1 = d.GetLength(0);
    int l2 = d.GetLength(1);
    for (int i = 0; i < l1; ++i)
        for (int j = 0; j < l2; ++j)
            sum += d[i, j];
    return sum;
}

public static double sum(double[][] d) {
    double sum = 0;
    for (int i = 0; i < d.Length; ++i)
        for (int j = 0; j < d[i].Length; ++j)
            sum += d[i][j];
    return sum;
}

public static void Main() {
    Random random = new Random();
    const int l1  = 1024, l2 = 1024;
    double[ ] d1  = new double[l1 * l2];
    double[,] d2  = new double[l1 , l2];
    double[][] d3 = new double[l1][];

    for (int i = 0; i < l1; ++i) {
        d3[i] = new double[l2];
        for (int j = 0; j < l2; ++j)
            d3[i][j] = d2[i, j] = d1[i * l2 + j] = random.NextDouble();
    }
    //
    const int iterations = 1000;
    TestTime(sum, d1, l1, iterations);
    TestTime(sum, d2, iterations);
    TestTime(sum, d3, iterations);
}

Complément d'enquête a montré que l'IL pour la deuxième méthode est de 23% plus grande que celle de la première méthode. (La taille du Code 68 vs 52.) Cela est principalement dû à des appels d' System.Array::GetLength(int). Le compilateur émet aussi des appels à l' Array::Get pour le déchiquetés un tableau multi-dimensionnel, alors qu'il appelle simplement ldelem , pour la simple tableau.

Alors je me demande, pourquoi est accessible par le biais de matrices multi-dimensionnelles plus lent que la normale tableaux? J'aurais cru le compilateur (ou JIT) serait de faire quelque chose de similaire à ce que j'ai fait dans ma première méthode, mais ce n'était pas vraiment le cas.

Pourriez-vous s'il vous plait m'aider à comprendre pourquoi cela se passe de cette manière?


Mise à jour: Suite à Henk Holterman la suggestion ici est la mise en œuvre de l' TestTime:

public static void TestTime<T, TR>(Func<T, TR> action, T obj,
                                   int iterations)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < iterations; ++i)
        action(obj);
    Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed);
}

public static void TestTime<T1, T2, TR>(Func<T1, T2, TR> action, T1 obj1,
                                        T2 obj2, int iterations)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < iterations; ++i)
        action(obj1, obj2);
    Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed);
}

50voto

Jon Skeet Points 692016

Unique dimensions des tableaux avec une limite inférieure de 0 sont d'un type différent de multi-dimensionnelle ou non 0 limite inférieure des tableaux à l'intérieur de l'IL (vector vs array IIRC). vector est plus simple de travailler avec - pour se rendre à l'élément x, vous venez de le faire pointer + size * x. Pour un array, vous avez à faire, pointer + size * (x-lower bound) pour un seul tableau unidimensionnel, et encore plus de l'arithmétique pour chaque dimension que vous ajoutez.

Fondamentalement, le CLR est optimisé pour le beaucoup plus le cas.

12voto

JeeBee Points 11882

Tableau vérifiant les limites?

Le tableau à une dimension a un membre de longueur auquel vous accédez directement. Lorsqu'il est compilé, il ne s'agit que d'une lecture en mémoire.

Le tableau multidimensionnel nécessite un appel à la méthode GetLength (int dimension) qui traite l'argument pour obtenir la longueur appropriée pour cette dimension. Cela ne compile pas jusqu'à une lecture en mémoire, vous obtenez donc un appel de méthode, etc.

De plus, GetLength (dimension int) effectuera une vérification des limites du paramètre.

5voto

Cameron Points 419

Interestly, j'ai couru le code suivant à partir de ci-dessus à l'aide de VS2008 NET3.5SP1 Win32 sur un Vista boîte, et dans la version/optimiser la différence est à peine mesurable, tout en debug/noopt le multi-dim tableaux ont été beaucoup plus lents. (J'ai couru les trois tests deux fois pour réduire JIT affecte le deuxième set.)

  Here are my numbers: 
    sum took 00:00:04.3356535
    sum took 00:00:04.1957663
    sum took 00:00:04.5523050
    sum took 00:00:04.0183060
    sum took 00:00:04.1785843 
    sum took 00:00:04.4933085

Regardez la deuxième série de trois numéros. La différence n'est pas assez pour moi de code tout dans la seule dimension des tableaux.

Bien que je n'ai pas posté, dans Debug/unoptimized multidimension vs unique/jagged ne faire une énorme différence.

Programme complet:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace single_dimension_vs_multidimension
{
    class Program
    {


        public static double sum(double[] d, int l1) {    // assuming the array is rectangular 
            double sum = 0; 
            int l2 = d.Length / l1; 
            for (int i = 0; i < l1; ++i)   
                for (int j = 0; j < l2; ++j)   
                    sum += d[i * l2 + j];   
            return sum;
        }

        public static double sum(double[,] d)
        {
            double sum = 0;  
            int l1 = d.GetLength(0);
            int l2 = d.GetLength(1);   
            for (int i = 0; i < l1; ++i)    
                for (int j = 0; j < l2; ++j)   
                    sum += d[i, j]; 
            return sum;
        }
        public static double sum(double[][] d)
        {
            double sum = 0;   
            for (int i = 0; i < d.Length; ++i) 
                for (int j = 0; j < d[i].Length; ++j) 
                    sum += d[i][j];
            return sum;
        }
        public static void TestTime<T, TR>(Func<T, TR> action, T obj, int iterations) 
        { 
            Stopwatch stopwatch = Stopwatch.StartNew();
            for (int i = 0; i < iterations; ++i)      
                action(obj);
            Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed);
        }
        public static void TestTime<T1, T2, TR>(Func<T1, T2, TR> action, T1 obj1, T2 obj2, int iterations)
        {
            Stopwatch stopwatch = Stopwatch.StartNew(); 
            for (int i = 0; i < iterations; ++i)    
                action(obj1, obj2); 
            Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed);
        }
        public static void Main() {   
            Random random = new Random(); 
            const int l1  = 1024, l2 = 1024; 
            double[ ] d1  = new double[l1 * l2]; 
            double[,] d2  = new double[l1 , l2];  
            double[][] d3 = new double[l1][];   
            for (int i = 0; i < l1; ++i)
            {
                d3[i] = new double[l2];   
                for (int j = 0; j < l2; ++j)  
                    d3[i][j] = d2[i, j] = d1[i * l2 + j] = random.NextDouble();
            }    
            const int iterations = 1000;
            TestTime<double[], int, double>(sum, d1, l1, iterations);
            TestTime<double[,], double>(sum, d2, iterations);

            TestTime<double[][], double>(sum, d3, iterations);
            TestTime<double[], int, double>(sum, d1, l1, iterations);
            TestTime<double[,], double>(sum, d2, iterations);
            TestTime<double[][], double>(sum, d3, iterations); 
        }

    }
}

3voto

Tamas Czinege Points 49277

Parce qu'un tableau multidimensionnel est juste un sucre syntaxique que c'est vraiment juste un tableau plat avec certaines de calcul de l'indice de la magie. D'autre part, un tableau en escalier, c'est comme, un tableau de tableaux. Avec un tableau à deux dimensions, l'accès à un élément impose à la lecture de la mémoire juste une fois, alors qu'avec deux niveaux de tableau en escalier, vous devez lire le mémoire deux fois.

EDIT: Apparemment l'affiche originale mélangé "les tableaux irréguliers" avec "multi-dimensions des tableaux" si mon raisonnement n'est pas exactement du stand. Pour la vraie raison, vérifiez Jon Skeet de l'artillerie lourde, la réponse ci-dessus.

2voto

AnthonyWJones Points 122520

Les tableaux dentelés sont des tableaux de références de classe (autres tableaux) jusqu'au tableau feuille, qui peut être un tableau de type primitif. Par conséquent, la mémoire allouée à chacun des autres tableaux peut être omniprésente.

Tandis qu'un tableau multidimensionnel a sa mémoire allouée en un seul bloc contigu.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X