Performances des tableaux par rapport aux listes

194

Disons que vous devez avoir une liste / un tableau d'entiers que vous avez besoin d'itérer fréquemment, et je veux dire extrêmement souvent. Les raisons peuvent varier, mais disons que c'est au cœur de la boucle la plus interne d'un traitement à volume élevé.

En général, on opterait pour l'utilisation de Lists (List) en raison de leur flexibilité de taille. En plus de cela, la documentation msdn affirme que les listes utilisent un tableau en interne et devraient fonctionner tout aussi rapidement (un coup d'œil rapide avec Reflector le confirme). Néanmoins, il y a des frais généraux impliqués.

Quelqu'un a-t-il réellement mesuré cela? itérer 6 millions de fois dans une liste prendrait-il le même temps qu'un tableau?

Boaz
la source
3
Mis à part les problèmes de performances, je préfère l'utilisation des tableaux aux listes pour leur taille fixe (dans les cas où la modification du nombre d'éléments n'est pas nécessaire, bien sûr). Lors de la lecture du code existant, je trouve utile de savoir rapidement qu'un élément est obligé d'avoir une taille fixe, plutôt que d'avoir à inspecter le code plus bas dans la fonction.
Warren Stevens
2
T[]vs List<T>peut faire une grande différence de performance. Je viens d'optimiser une application extrêmement intensive en boucles (imbriquées) pour passer des listes aux tableaux sur .NET 4.0. Je m'attendais peut-être à une amélioration de 5% à 10%, mais j'ai eu plus de 40% d'accélération! Aucun autre changement que de passer directement d'une liste à l'autre. Toutes les énumérations ont été faites avec des foreachdéclarations. Sur la base de la réponse de Marc Gravell, il ressemble foreachavec List<T>est particulièrement mauvaise.
Special Sauce

Réponses:

221

Très facile à mesurer ...

Dans un petit nombre de code de traitement en boucle serrée où je sais que la longueur est fixe, j'utilise des tableaux pour ce tout petit peu de micro-optimisation; les tableaux peuvent être légèrement plus rapides si vous utilisez le formulaire indexeur / for - mais l'IIRC pense que cela dépend du type de données dans le tableau. Mais à moins que vous n'ayez besoin de micro-optimiser, restez simple et utilisez, List<T>etc.

Bien sûr, cela ne s'applique que si vous lisez toutes les données; un dictionnaire serait plus rapide pour les recherches basées sur les clés.

Voici mes résultats en utilisant "int" (le deuxième nombre est une somme de contrôle pour vérifier qu'ils ont tous fait le même travail):

(modifié pour corriger le bogue)

List/for: 1971ms (589725196)
Array/for: 1864ms (589725196)
List/foreach: 3054ms (589725196)
Array/foreach: 1860ms (589725196)

basé sur le banc d'essai:

using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(12345);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next(5000));
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}
Marc Gravell
la source
8
Détail intéressant: voici les temps RELEASE / DEBUG sur mon rig (.net 3.5 sp1): 0.92, 0.80, 0.96, 0.93; ce qui me dit qu'il y a une certaine intelligence dans la VM pour optimiser la boucle Array / for environ 10% mieux que les autres cas.
David Schmitt
2
Oui, il existe une optimisation JIT pour array / for. En fait, j'avais l'impression qu'il incluait le boîtier Length (car il sait qu'il est corrigé), d'où la raison pour laquelle je ne l'ai pas retiré en premier (contrairement à List où je l'ai fait). Merci pour la mise à jour.
Marc Gravell
2
Bizarre. Mes tests très similaires ne montrent aucune différence significative entre for et foreach lors de l'utilisation de tableaux. Je vais enquêter de manière approfondie dans un article de blog avec une référence que d'autres personnes peuvent exécuter et m'envoyer des résultats pour ...
Jon Skeet
1
J'obtiens des résultats radicalement différents si j'utilise une variable différente pour chk pour chaque test (chk1, chk2, chk3, chk4). List / for = 1303ms, Array / for = 433ms. Des idées pourquoi?
Jon
4
Le lien mentionné dans le commentaire ci-dessus de Jon vers le blog de Jon Skeet a été rompu. Ci-dessous le lien mis à jour. codeblog.jonskeet.uk/2009/01/29/…
Josh DeLong
88

Résumé:

  • Array doit utiliser:

    • Aussi souvent que possible. C'est rapide et prend la plus petite plage de RAM pour la même quantité d'informations.
    • Si vous connaissez le nombre exact de cellules nécessaires
    • Si les données enregistrées dans le tableau <85000 b (85000/32 = 2656 éléments pour les données entières)
    • Si nécessaire, vitesse d'accès aléatoire élevée
  • Liste à utiliser:

    • Si nécessaire, ajouter des cellules à la fin de la liste (souvent)
    • Si nécessaire, ajouter des cellules au début / au milieu de la liste (PAS SOUVENT)
    • Si les données enregistrées dans le tableau <85000 b (85000/32 = 2656 éléments pour les données entières)
    • Si nécessaire, vitesse d'accès aléatoire élevée
  • LinkedList doit utiliser:

    • Si nécessaire, ajouter des cellules au début / au milieu / à la fin de la liste (souvent)

    • Si nécessaire, uniquement un accès séquentiel (avant / arrière)

    • Si vous devez enregistrer de GRANDS éléments, mais que le nombre d'éléments est faible.

    • Mieux vaut ne pas l'utiliser pour une grande quantité d'éléments, car il utilise de la mémoire supplémentaire pour les liens.

      Si vous n'êtes pas sûr d'avoir besoin de LinkedList, vous n'en avez pas besoin.


Plus de détails:

signification de la couleur

Tableau vs liste vs liste liée

Beaucoup plus de détails:

https://stackoverflow.com/a/29263914/4423545

Andrew
la source
Je suis légèrement confus par votre affirmation selon laquelle le préfixe de la liste est relativement rapide mais l'insertion est lente. L'insertion est également un temps linéaire et plus rapide de 50% en moyenne que le préfixe.
Mike Marynowski
1
@MikeMarynowski dans la liste c # englobe Array. Donc, en cas d'insertion dans la liste, vous n'aurez un temps linéaire que jusqu'à un certain point. Après ce système créera un nouveau tableau plus grand et aura besoin de temps pour copier les éléments de l'ancien.
Andrew
Même chose avec les préfixes.
Mike Marynowski
Une opération de préfixe est juste un insert à 0. C'est le pire des cas d'insertion en termes de performances, donc si l'insertion est lente, le préfixe est encore plus lent.
Mike Marynowski
L'insertion et le préfixe sont tous deux O (n) (amorti). Un préfixe EST une insertion, mais c'est l'insertion la plus lente possible car elle doit déplacer TOUS les éléments de la liste d'un endroit vers le haut. Une insertion dans un emplacement aléatoire n'a qu'à remonter les éléments qui sont à un index supérieur au point d'insertion, donc 50% des éléments en moyenne.
Mike Marynowski
26

Je pense que la performance sera assez similaire. La surcharge qui est impliquée lors de l'utilisation d'une liste par rapport à un tableau est, à mon humble avis, lorsque vous ajoutez des éléments à la liste et lorsque la liste doit augmenter la taille du tableau qu'elle utilise en interne, lorsque la capacité du tableau est atteinte.

Supposons que vous ayez une liste avec une capacité de 10, alors la liste augmentera sa capacité une fois que vous voudrez ajouter le 11e élément. Vous pouvez réduire l'impact sur les performances en initialisant la capacité de la liste au nombre d'éléments qu'elle contiendra.

Mais, pour savoir si l'itération sur une liste est aussi rapide que l'itération sur un tableau, pourquoi ne pas le comparer?

int numberOfElements = 6000000;

List<int> theList = new List<int> (numberOfElements);
int[] theArray = new int[numberOfElements];

for( int i = 0; i < numberOfElements; i++ )
{
    theList.Add (i);
    theArray[i] = i;
}

Stopwatch chrono = new Stopwatch ();

chrono.Start ();

int j;

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theList[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the List took {0} msec", chrono.ElapsedMilliseconds));

 chrono.Reset();

 chrono.Start();

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theArray[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the array took {0} msec", chrono.ElapsedMilliseconds));

 Console.ReadLine();

Sur mon système; l'itération sur le tableau prenait 33 ms; itérer sur la liste a pris 66 ms.

Pour être honnête, je ne m'attendais pas à ce que la variation soit autant. Donc, j'ai mis mon itération en boucle: maintenant, j'exécute les deux itérations 1000 fois. Les résultats sont:

l'itération de la liste a pris 67146 ms L'itération du tableau a pris 40821 msec

Maintenant, la variation n'est plus si grande, mais quand même ...

Par conséquent, j'ai démarré .NET Reflector et le getter de l'indexeur de la classe List ressemble à ceci:

public T get_Item(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    return this._items[index];
}

Comme vous pouvez le voir, lorsque vous utilisez l'indexeur de la liste, la liste vérifie si vous ne sortez pas des limites du tableau interne. Ce chèque supplémentaire a un coût.

Frederik Gheysels
la source
Salut Frederik, merci! Comment expliqueriez-vous que votre liste prenne le double du temps des tableaux. Pas ce à quoi vous vous attendriez. Avez-vous essayé d'augmenter le nombre d'éléments?
Boaz
1
Ne renverrait pas this._items [index]; jeter déjà une exception si l'index était hors de portée? Pourquoi .NET a-t-il cette vérification supplémentaire lorsque le résultat final est le même avec ou sans lui?
John Mercier
@John Mercier le contrôle est par rapport à la taille de la liste (nombre d'éléments actuellement contenus), qui est différente et probablement inférieure à la capacité du tableau _items. La baie est allouée avec une capacité excédentaire pour accélérer l'ajout d'éléments futurs en ne nécessitant pas de réallocation pour chaque ajout.
Trasvi
21

si vous n'obtenez qu'une seule valeur de l'un ou l'autre (pas dans une boucle), alors les deux vérifient les limites (vous êtes dans le code managé, rappelez-vous), c'est juste la liste qui le fait deux fois. Voir les notes plus tard pour savoir pourquoi ce n'est probablement pas un gros problème.

Si vous utilisez le vôtre pour (int int i = 0; i <x. [Length / Count]; i ++), la principale différence est la suivante:

  • Tableau:
    • la vérification des limites est supprimée
  • Listes
    • la vérification des limites est effectuée

Si vous utilisez foreach, la principale différence est la suivante:

  • Tableau:
    • aucun objet n'est alloué pour gérer l'itération
    • la vérification des limites est supprimée
  • List via une variable connue sous le nom de List.
    • la variable de gestion des itérations est allouée par pile
    • la vérification des limites est effectuée
  • Liste via une variable connue pour être IList.
    • la variable de gestion des itérations est allouée au tas
    • La vérification des limites est également effectuée Les valeurs des listes ne peuvent pas être modifiées pendant le foreach alors que celles du tableau peuvent l'être.

La vérification des limites n'est souvent pas un problème (surtout si vous êtes sur un processeur avec un pipeline profond et une prédiction de branche - la norme pour la plupart de nos jours), mais seul votre propre profilage peut vous dire si c'est un problème. Si vous êtes dans des parties de votre code où vous évitez les allocations de tas (les bons exemples sont des bibliothèques ou des implémentations de hashcode), vous assurer que la variable est typée comme List pas IList évitera cet écueil. Comme toujours profil si cela compte.

ShuggyCoUk
la source
11

[ Voir aussi cette question ]

J'ai modifié la réponse de Marc pour utiliser des nombres aléatoires réels et faire le même travail dans tous les cas.

Résultats:

         pour foreach
Baie: 1575 ms 1575 ms (+ 0%)
Liste: 1630ms 2627ms (+ 61%)
         (+ 3%) (+ 67%)

(Somme de contrôle: -1000038876)

Compilé comme version sous VS 2008 SP1. Exécution sans débogage sur un [email protected], .NET 3.5 SP1.

Code:

class Program
{
    static void Main(string[] args)
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(1);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next());
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = arr.Length;
            for (int i = 0; i < len; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
        Console.WriteLine();

        Console.ReadLine();
    }
}
David Schmitt
la source
C'est bizarre - je viens d'exécuter votre code exact, construit à partir de la ligne de commande (3.5SP1) avec / o + / debug- et mes résultats sont: list / for: 1524; tableau / pour: 1472; list / foreach: 4128; array / foreach: 1484.
Jon Skeet
Vous dites que cela a été compilé en tant que version - puis-je simplement vérifier que vous l'avez exécuté plutôt que de le déboguer? Question idiote, je sais, mais je ne peux pas expliquer les résultats autrement ...
Jon Skeet
2

Les mesures sont bonnes, mais vous obtiendrez des résultats très différents en fonction de ce que vous faites exactement dans votre boucle intérieure. Mesurez votre propre situation. Si vous utilisez le multi-threading, cela seul est une activité non triviale.

Stéphan Eggermont
la source
2

En effet, si vous effectuez des calculs complexes à l'intérieur de la boucle, les performances de l'indexeur de tableau par rapport à l'indexeur de liste peuvent être si marginales que finalement, cela n'a pas d'importance.

Frederik Gheysels
la source
2

En voici un qui utilise des dictionnaires, IEnumerable:

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

static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);

        for (int i = 0; i < 6000000; i++)
        {
                list.Add(i);
        }
        Console.WriteLine("Count: {0}", list.Count);

        int[] arr = list.ToArray();
        IEnumerable<int> Ienumerable = list.ToArray();
        Dictionary<int, bool> dict = list.ToDictionary(x => x, y => true);

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }

        Console.WriteLine("Ienumerable/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }

        Console.WriteLine("Dict/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);


        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }

        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);



        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Ienumerable/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Dict/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}
Travis
la source
2

N'essayez pas d'ajouter de la capacité en augmentant le nombre d'éléments.

Performance

List For Add: 1ms
Array For Add: 2397ms

    Stopwatch watch;
        #region --> List For Add <--

        List<int> intList = new List<int>();
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            intList.Add(rand.Next());
        }
        watch.Stop();
        Console.WriteLine("List For Add: {0}ms", watch.ElapsedMilliseconds);
        #endregion

        #region --> Array For Add <--

        int[] intArray = new int[0];
        watch = Stopwatch.StartNew();
        int sira = 0;
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            sira += 1;
            Array.Resize(ref intArray, intArray.Length + 1);
            intArray[rpt] = rand.Next();

        }
        watch.Stop();
        Console.WriteLine("Array For Add: {0}ms", watch.ElapsedMilliseconds);

        #endregion
Fatih GÜRDAL
la source
Je redimensionner un tableau 60k fois va être lent ... Sûrement dans le monde réel, l'approche serait simplement de vérifier le nombre d'emplacements supplémentaires dont vous avez besoin, de le redimensionner à une longueur de + 60k, puis de parcourir les inserts.
tobriand
Le redimensionnement d'un tableau est très rapide si vous doublez la taille chaque fois que vous constatez que vous avez besoin de plus d'espace. J'ai trouvé que cela semble prendre à peu près le même temps que de le redimensionner une fois après la déclaration initiale. Cela vous donne la flexibilité d'une liste et la plupart de la vitesse d'un tableau.
user1318499
2

J'avais peur que les Benchmarks publiés dans d'autres réponses ne laissent encore de la place au compilateur pour optimiser, éliminer ou fusionner les boucles, alors j'en ai écrit une qui:

  • Utilisé des entrées imprévisibles (aléatoires)
  • Exécute un calculé avec le résultat imprimé sur la console
  • Modifie les données d'entrée à chaque répétition

Le résultat est qu'un tableau direct a des performances environ 250% meilleures qu'un accès à un tableau encapsulé dans un IList:

  • 1 milliard d'accès au tableau: 4000 ms
  • 1 milliard d'accès à la liste: 10000 ms
  • 100 millions d'accès au tableau: 350 ms
  • 100 millions d'accès aux listes: 1000 ms

Voici le code:

static void Main(string[] args) {
  const int TestPointCount = 1000000;
  const int RepetitionCount = 1000;

  Stopwatch arrayTimer = new Stopwatch();
  Stopwatch listTimer = new Stopwatch();

  Point2[] points = new Point2[TestPointCount];
  var random = new Random();
  for (int index = 0; index < TestPointCount; ++index) {
    points[index].X = random.NextDouble();
    points[index].Y = random.NextDouble();
  }

  for (int repetition = 0; repetition <= RepetitionCount; ++repetition) {
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Start();
    }
    doWorkOnArray(points);
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Stop();
    }

    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Start();
    }
    doWorkOnList(points);
    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Stop();
    }
  }

  Console.WriteLine("Ignore this: " + points[0].X + points[0].Y);
  Console.WriteLine(
    string.Format(
      "{0} accesses on array took {1} ms",
      RepetitionCount * TestPointCount, arrayTimer.ElapsedMilliseconds
    )
  );
  Console.WriteLine(
    string.Format(
      "{0} accesses on list took {1} ms",
      RepetitionCount * TestPointCount, listTimer.ElapsedMilliseconds
    )
  );

}

private static void doWorkOnArray(Point2[] points) {
  var random = new Random();

  int pointCount = points.Length;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

private static void doWorkOnList(IList<Point2> points) {
  var random = new Random();

  int pointCount = points.Count;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}
Cygone
la source
0

Puisque List <> utilise des tableaux en interne, les performances de base doivent être les mêmes. Deux raisons pour lesquelles la liste pourrait être légèrement plus lente:

  • Pour rechercher un élément dans la liste, une méthode de List est appelée, qui effectue la recherche dans le tableau sous-jacent. Vous avez donc besoin d'un appel de méthode supplémentaire. D'un autre côté, le compilateur peut reconnaître cela et optimiser l'appel "inutile".
  • Le compilateur peut faire des optimisations spéciales s'il connaît la taille du tableau, ce qu'il ne peut pas faire pour une liste de longueur inconnue. Cela peut améliorer les performances si vous n'avez que quelques éléments dans votre liste.

Pour vérifier si cela fait une différence pour vous, il est probablement préférable d'ajuster les fonctions de chronométrage publiées à une liste de la taille que vous prévoyez d'utiliser et de voir comment sont les résultats pour votre cas particulier.

qc
la source
0

Depuis que j'avais une question similaire, cela m'a permis de démarrer rapidement.

Ma question est un peu plus précise, `` quelle est la méthode la plus rapide pour une implémentation de tableau réflexif ''

Les tests effectués par Marc Gravell montrent beaucoup de choses, mais pas exactement le timing d'accès. Son timing inclut également le bouclage sur les tableaux et les listes. Depuis que j'ai également proposé une troisième méthode que je voulais tester, un «dictionnaire», juste pour comparer, j'ai étendu le code de test hist.

Tout d'abord, je fais un test en utilisant une constante, ce qui me donne un certain timing incluant la boucle. Il s'agit d'un timing «nu», à l'exclusion de l'accès réel. Ensuite, je fais un test avec l'accès à la structure du sujet, cela me donne un timing, une boucle et un accès réel "overhead inclus".

La différence entre la synchronisation «nue» et la synchronisation «sans frais généraux» me donne une indication de la synchronisation «d'accès à la structure».

Mais quelle est la précision de ce timing? Pendant les tests, les fenêtres feront un certain temps de découpage pour shure. Je n'ai aucune information sur le découpage temporel, mais je suppose qu'il est uniformément réparti pendant le test et de l'ordre de dizaines de msec, ce qui signifie que la précision de la synchronisation doit être de l'ordre de +/- 100 msec environ. Une estimation un peu approximative? Quoi qu'il en soit, une source d'erreur de mesure systématique.

De plus, les tests ont été effectués en mode 'Debug' sans optimisation. Sinon, le compilateur pourrait changer le code de test réel.

Donc, j'obtiens deux résultats, un pour une constante, marqué «(c)», et un pour l'accès marqué «(n)» et la différence «dt» me dit combien de temps l'accès réel prend.

Et voici les résultats:

          Dictionary(c)/for: 1205ms (600000000)
          Dictionary(n)/for: 8046ms (589725196)
 dt = 6841

                List(c)/for: 1186ms (1189725196)
                List(n)/for: 2475ms (1779450392)
 dt = 1289

               Array(c)/for: 1019ms (600000000)
               Array(n)/for: 1266ms (589725196)
 dt = 247

 Dictionary[key](c)/foreach: 2738ms (600000000)
 Dictionary[key](n)/foreach: 10017ms (589725196)
 dt = 7279

            List(c)/foreach: 2480ms (600000000)
            List(n)/foreach: 2658ms (589725196)
 dt = 178

           Array(c)/foreach: 1300ms (600000000)
           Array(n)/foreach: 1592ms (589725196)
 dt = 292


 dt +/-.1 sec   for    foreach
 Dictionary     6.8       7.3
 List           1.3       0.2
 Array          0.2       0.3

 Same test, different system:
 dt +/- .1 sec  for    foreach
 Dictionary     14.4   12.0
       List      1.7    0.1
      Array      0.5    0.7

Avec de meilleures estimations sur les erreurs de synchronisation (comment supprimer l'erreur de mesure systématique due au découpage dans le temps?), On pourrait en dire plus sur les résultats.

Il semble que List / foreach a l'accès le plus rapide, mais la surcharge le tue.

La différence entre List / for et List / foreach est étrange. Peut-être qu'un encaissement est impliqué?

De plus, pour accéder à un tableau, peu importe si vous utilisez une forboucle ou une foreachboucle. Les résultats de synchronisation et leur précision rendent les résultats «comparables».

Utiliser un dictionnaire est de loin le plus lent, je ne l'ai considéré que parce que sur le côté gauche (l'indexeur) j'ai une liste clairsemée d'entiers et non une plage comme celle utilisée dans ces tests.

Voici le code de test modifié.

Dictionary<int, int> dict = new Dictionary<int, int>(6000000);
List<int> list = new List<int>(6000000);
Random rand = new Random(12345);
for (int i = 0; i < 6000000; i++)
{
    int n = rand.Next(5000);
    dict.Add(i, n);
    list.Add(n);
}
int[] arr = list.ToArray();

int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // dict[i];
    }
}
watch.Stop();
long c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += dict[i];
    }
}
watch.Stop();
long n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // list[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(c)/for: {0}ms ({1})", c_dt, chk);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += list[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += 1; // arr[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("              Array(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += arr[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += 1; // dict[i]; ;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += dict[i]; ;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("          Array(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);
PapaAtHome
la source
0

Dans quelques brefs tests, j'ai trouvé qu'une combinaison des deux était meilleure dans ce que j'appellerais des mathématiques raisonnablement intensives:

Type: List<double[]>

Heure: 00: 00: 05.1861300

Type: List<List<double>>

Heure: 00: 00: 05.7941351

Type: double[rows * columns]

Heure: 00: 00: 06.0547118

Exécution du code:

int rows = 10000;
int columns = 10000;

IMatrix Matrix = new IMatrix(rows, columns);

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();


for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] = Math.E;

for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] *= -Math.Log(Math.E);


stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;

Console.WriteLine(ts.ToString());

Je souhaite que nous ayons des classes matricielles accélérées par matériel de premier ordre comme l'équipe .NET l'a fait avec la System.Numerics.Vectorsclasse!

C # pourrait être le meilleur langage ML avec un peu plus de travail dans ce domaine!

clou rouillé
la source