Y a-t-il un impact sur les performances lors de l'appel de ToList ()?

139

Lors de l'utilisation ToList(), y a-t-il un impact sur les performances qui doit être pris en compte?

J'écrivais une requête pour récupérer des fichiers à partir d'un répertoire, qui est la requête:

string[] imageArray = Directory.GetFiles(directory);

Cependant, comme j'aime travailler avec à la List<>place, j'ai décidé de mettre en place ...

List<string> imageList = Directory.GetFiles(directory).ToList();

Alors, y a-t-il une sorte d'impact sur les performances à prendre en compte au moment de décider de faire une conversion comme celle-ci - ou à ne prendre en compte que lorsqu'il s'agit d'un grand nombre de fichiers? Est-ce une conversion négligeable?

Cody
la source
+1 intéressé à connaître la réponse ici aussi. À mon humble avis, à moins que l'application ne soit critique pour les performances, je pense que j'utiliserais toujours un List<T>en faveur d'un T[]si cela rend le code plus logique / lisible / maintenable (à moins bien sûr que la conversion ne cause des problèmes de performances notables , auquel cas je re visitez-le je suppose).
Sepster
Créer une liste à partir d'un tableau devrait être très bon marché.
leppie
2
@Sepster Je spécifie uniquement le type de données aussi précisément que je dois faire un travail. Si je n'ai pas à appeler Addou Remove, je le laisserais comme IEnumerable<T>(ou même mieux var)
pswg
4
Je pense que dans ce cas, il est préférable d'appeler EnumerateFilesau lieu de GetFiles, donc un seul tableau sera créé.
tukaef
3
GetFiles(directory), comme il est implémenté dans .NET actuellement, à peu près le fait new List<string>(EnumerateFiles(directory)).ToArray(). Alors GetFiles(directory).ToList()crée une liste, crée un tableau à partir de cela, puis crée à nouveau une liste. Comme le dit 2kay, vous devriez préférer le faire EnumerateFiles(directory).ToList()ici.
Joren

Réponses:

178

IEnumerable.ToList()

Oui, IEnumerable<T>.ToList()a un impact sur les performances, il s'agit d'une opération O (n) bien qu'elle ne nécessitera probablement d'attention que dans les opérations critiques pour les performances.

L' ToList()opération utilisera le List(IEnumerable<T> collection)constructeur. Ce constructeur doit faire une copie du tableau (plus généralement IEnumerable<T>), sinon les futures modifications du tableau d'origine changeront également sur la source, T[]ce qui ne serait généralement pas souhaitable.

Je voudrais répéter que cela ne fera une différence qu'avec une liste énorme, la copie de morceaux de mémoire est une opération assez rapide à effectuer.

Astuce pratique, AsvsTo

Vous remarquerez que dans LINQ il existe plusieurs méthodes qui commencent par As(comme AsEnumerable()) et To(comme ToList()). Les méthodes qui commencent par Tonécessitent une conversion comme ci-dessus (c'est-à-dire peuvent avoir un impact sur les performances), et les méthodes qui commencent par Asne nécessitent pas et nécessiteront juste une opération de conversion ou simple.

Détails supplémentaires sur List<T>

Voici un peu plus de détails sur le List<T>fonctionnement au cas où vous seriez intéressé :)

A List<T>utilise également une construction appelée tableau dynamique qui doit être redimensionnée à la demande, cet événement de redimensionnement copie le contenu d'un ancien tableau dans le nouveau tableau. Il commence donc petit et augmente sa taille si nécessaire .

C'est la différence entre les attributs Capacityet Countsur List<T>. Capacityfait référence à la taille du tableau dans les coulisses, Countest le nombre d'éléments dans le List<T>qui est toujours <= Capacity. Ainsi, lorsqu'un élément est ajouté à la liste, en l'augmentant Capacity, la taille du List<T>est doublée et le tableau est copié.

Daniel Imms
la source
2
Je voulais juste souligner que le List(IEnumerable<T> collection)constructeur vérifie si le paramètre de collection est ICollection<T>, puis crée immédiatement un nouveau tableau interne avec la taille requise. Si la collection de paramètres ne l'est pas ICollection<T>, le constructeur l'itère et appelle Addchaque élément.
Justinas Simanavicius
Il est important de noter que vous pouvez souvent voir ToList () comme une opération trompeusement exigeante. Cela se produit lorsque vous créez un IEnumerable <> via une requête LINQ. la requête linq est construite mais pas exécutée. appeler ToList () exécutera la requête et semblera donc gourmande en ressources - mais c'est la requête qui est intensive et non l'opération ToList () (sauf si c'est une liste vraiment énorme)
dancer42
36

Y a-t-il un impact sur les performances lors de l'appel à toList ()?

Oui bien sûr. Théoriquement, a même i++un impact sur les performances, cela ralentit le programme pendant peut-être quelques tiques.

Que fait .ToList-on?

Lorsque vous .ToListappelez, le code appelle Enumerable.ToList()qui est une méthode d'extension qui return new List<TSource>(source). Dans le constructeur correspondant, dans les pires circonstances, il parcourt le conteneur d'éléments et les ajoute un par un dans un nouveau conteneur. Son comportement affecte donc peu les performances. Il est impossible d'être un goulot de bouteille performant de votre application.

Quel est le problème avec le code de la question

Directory.GetFilesparcourt le dossier et renvoie immédiatement les noms de tous les fichiers en mémoire, il y a un risque potentiel que la chaîne [] coûte beaucoup de mémoire, ralentissant tout.

Que faut-il faire alors

Ça dépend. Si vous (ainsi que votre logique métier) garantissez que la quantité de fichier dans le dossier est toujours petite, le code est acceptable. Mais il est toujours suggéré d'utiliser une version paresseuse: Directory.EnumerateFilesen C # 4. Cela ressemble beaucoup plus à une requête, qui ne sera pas exécutée immédiatement, vous pouvez y ajouter plus de requête comme:

Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile"))

qui arrêtera de chercher le chemin dès qu'un fichier dont le nom contient "myfile" sera trouvé. C'est évidemment une meilleure performance alors .GetFiles.

Cheng Chen
la source
19

Y a-t-il un impact sur les performances lors de l'appel à toList ()?

Oui il y a. L'utilisation de la méthode d'extension Enumerable.ToList()construira un nouvel List<T>objet à partir de la IEnumerable<T>collection source, ce qui aura bien sûr un impact sur les performances.

Cependant, la compréhension List<T>peut vous aider à déterminer si l'impact sur les performances est significatif.

List<T>utilise un tableau ( T[]) pour stocker les éléments de la liste. Les tableaux ne peuvent pas être étendus une fois alloués, List<T>ils utiliseront donc un tableau surdimensionné pour stocker les éléments de la liste. Lorsque la List<T>taille dépasse la taille du tableau sous-jacent, un nouveau tableau doit être alloué et le contenu de l'ancien tableau doit être copié dans le nouveau tableau plus grand avant que la liste puisse s'agrandir.

Lorsqu'un nouveau List<T>est construit à partir d'un, IEnumerable<T>il y a deux cas:

  1. La collection source implements ICollection<T>: Then ICollection<T>.Countest utilisée pour obtenir la taille exacte de la collection source et un tableau de sauvegarde correspondant est alloué avant que tous les éléments de la collection source ne soient copiés dans le tableau de sauvegarde à l'aide de ICollection<T>.CopyTo(). Cette opération est assez efficace et correspondra probablement à une instruction CPU pour copier des blocs de mémoire. Cependant, en termes de performances, de la mémoire est nécessaire pour le nouveau tableau et des cycles CPU sont nécessaires pour copier tous les éléments.

  2. Sinon, la taille de la collection source est inconnue et l'énumérateur de IEnumerable<T>est utilisé pour ajouter chaque élément source un par un au nouveau List<T>. Initialement, la matrice de sauvegarde est vide et une matrice de taille 4 est créée. Ensuite, lorsque ce tableau est trop petit, la taille est doublée de sorte que le tableau de sauvegarde croît comme ceci 4, 8, 16, 32 etc. Chaque fois que le tableau de sauvegarde grandit, il doit être réalloué et tous les éléments stockés jusqu'à présent doivent être copiés. Cette opération est beaucoup plus coûteuse par rapport au premier cas où un tableau de la bonne taille peut être créé immédiatement.

    De plus, si votre collection source contient, disons 33 éléments, la liste finira par utiliser un tableau de 64 éléments gaspillant de la mémoire.

Dans votre cas, la collection source est un tableau qui implémente ICollection<T>donc l'impact sur les performances n'est pas quelque chose dont vous devriez vous préoccuper, sauf si votre tableau source est très grand. L'appel ToList()copiera simplement le tableau source et l'enveloppera dans un List<T>objet. Même les performances du deuxième boîtier ne sont pas un sujet de préoccupation pour les petites collections.

Martin Liversage
la source
5

"Y a-t-il un impact sur les performances à prendre en compte?"

Le problème avec votre scénario précis est que votre véritable préoccupation concernant les performances serait d'abord et avant tout la vitesse du disque dur et l'efficacité du cache du disque.

De ce point de vue, l'impact est sûrement négligeable au point que NON il ne doit pas être considéré.

MAIS UNIQUEMENT si vous avez vraiment besoin des fonctionnalités de la List<>structure pour éventuellement vous rendre plus productif, ou votre algorithme plus convivial, ou un autre avantage. Sinon, vous ajoutez simplement un coup de performance insignifiant, sans aucune raison. Dans ce cas, naturellement, vous ne devriez pas le faire! :)

jross
la source
4

ToList()crée une nouvelle liste et y place les éléments, ce qui signifie qu'il y a un coût associé à l'action ToList(). Dans le cas d'une petite collection, le coût ne sera pas très perceptible, mais avoir une énorme collection peut entraîner une baisse des performances en cas d'utilisation de ToList.

En règle générale, vous ne devez pas utiliser ToList () sauf si le travail que vous effectuez ne peut pas être effectué sans convertir la collection en List. Par exemple, si vous souhaitez simplement parcourir la collection, vous n'avez pas besoin d'effectuer ToList

Si vous effectuez des requêtes sur une source de données, par exemple une base de données utilisant LINQ to SQL, le coût de création de ToList est beaucoup plus élevé, car lorsque vous utilisez ToList avec LINQ to SQL au lieu de faire une exécution différée, c'est-à-dire charger des éléments si nécessaire (ce qui peut être bénéfique dans de nombreux scénarios), il charge instantanément les éléments de la base de données en mémoire

Haris Hasan
la source
Haris: ce que je ne suis pas sûr de la source originale ce qui arrivera à la source originale après avoir appelé la ToList ()
TalentTuner
@Saurabh GC va le nettoyer
pswg
@Saurabh rien n'arrivera à la source originale. Les éléments de la source originale seront référencés par la liste nouvellement créée
Haris Hasan
"si vous voulez simplement parcourir la collection, vous n'avez pas besoin d'exécuter ToList" - alors comment devez-vous itérer?
SharpC
4

Ce sera aussi (in) efficace que de faire:

var list = new List<T>(items);

Si vous démontez le code source du constructeur qui prend an IEnumerable<T>, vous verrez qu'il fera plusieurs choses:

  • Appelez collection.Count, donc si collectionest an IEnumerable<T>, cela forcera l'exécution. Si collectionest un tableau, une liste, etc., il devrait l'être O(1).

  • S'il est collectionimplémenté ICollection<T>, il enregistrera les éléments dans un tableau interne à l'aide de la ICollection<T>.CopyTométhode. Cela devrait être O(n), étant nla longueur de la collection.

  • Si elle collectionn'est pas implémentée ICollection<T>, elle parcourra les éléments de la collection et les ajoutera à une liste interne.

Donc, oui, cela consommera plus de mémoire, car il doit créer une nouvelle liste, et dans le pire des cas, ce sera le casO(n) , car il va parcourir le collectionpour faire une copie de chaque élément.

Oscar Mederos
la source
3
close, 0(n)nest la somme totale des octets occupés par les chaînes de la collection d'origine, et non le nombre d'éléments (enfin, pour être plus exact, n = octets / taille du mot)
user1416420
@ user1416420 Je me trompe peut-être, mais pourquoi? Que faire s'il est une collection d'un autre type (par exemple. bool, int, Etc.)? Vous n'avez pas vraiment besoin de faire une copie de chaque chaîne de la collection. Vous venez de les ajouter à la nouvelle liste.
Oscar Mederos
la nouvelle allocation de mémoire et la copie d'octets n'ont toujours pas d'importance, c'est ce qui tue cette méthode. Un booléen occupera également 4 octets dans .NET. En fait, chaque référence d'un objet dans .NET fait au moins 8 octets de long, donc c'est assez lent. les 4 premiers octets pointent vers la table des types et les 4 derniers octets pointent vers la valeur ou l'emplacement de mémoire où trouver la valeur
user1416420
3

Compte tenu de la performance de la récupération de la liste de fichiers, ToList()est négligeable. Mais pas vraiment pour d'autres scénarios. Cela dépend vraiment de l'endroit où vous l'utilisez.

  • Lorsque vous appelez un tableau, une liste ou une autre collection, vous créez une copie de la collection en tant que fichier List<T>. Les performances ici dépendent de la taille de la liste. Vous devriez le faire lorsque cela est vraiment nécessaire.

    Dans votre exemple, vous l'appelez sur un tableau. Il parcourt le tableau et ajoute les éléments un par un à une liste nouvellement créée. Ainsi, l'impact sur les performances dépend du nombre de fichiers.

  • Lorsque vous appelez un IEnumerable<T>, vous matérialisez le IEnumerable<T>(généralement une requête).

Mohammad Dehghan
la source
2

ToList créera une nouvelle liste et copiera les éléments de la source d'origine vers la liste nouvellement créée, de sorte que la seule chose à faire est de copier les éléments de la source d'origine et dépend de la taille de la source

TalentTuner
la source