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?
c#
arrays
performance
list
Cody
la source
la source
List<T>
en faveur d'unT[]
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).Add
ouRemove
, je le laisserais commeIEnumerable<T>
(ou même mieuxvar
)EnumerateFiles
au lieu deGetFiles
, donc un seul tableau sera créé.GetFiles(directory)
, comme il est implémenté dans .NET actuellement, à peu près le faitnew List<string>(EnumerateFiles(directory)).ToArray()
. AlorsGetFiles(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 faireEnumerateFiles(directory).ToList()
ici.Réponses:
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 leList(IEnumerable<T> collection)
constructeur. Ce constructeur doit faire une copie du tableau (plus généralementIEnumerable<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,
As
vsTo
Vous remarquerez que dans LINQ il existe plusieurs méthodes qui commencent par
As
(commeAsEnumerable()
) etTo
(commeToList()
). Les méthodes qui commencent parTo
nécessitent une conversion comme ci-dessus (c'est-à-dire peuvent avoir un impact sur les performances), et les méthodes qui commencent parAs
ne 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
Capacity
etCount
surList<T>
.Capacity
fait référence à la taille du tableau dans les coulisses,Count
est le nombre d'éléments dans leList<T>
qui est toujours<= Capacity
. Ainsi, lorsqu'un élément est ajouté à la liste, en l'augmentantCapacity
, la taille duList<T>
est doublée et le tableau est copié.la source
List(IEnumerable<T> collection)
constructeur vérifie si le paramètre de collection estICollection<T>
, puis crée immédiatement un nouveau tableau interne avec la taille requise. Si la collection de paramètres ne l'est pasICollection<T>
, le constructeur l'itère et appelleAdd
chaque élément.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
.ToList
appelez, le code appelleEnumerable.ToList()
qui est une méthode d'extension quireturn 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.GetFiles
parcourt 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.EnumerateFiles
en 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: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
.la source
Oui il y a. L'utilisation de la méthode d'extension
Enumerable.ToList()
construira un nouvelList<T>
objet à partir de laIEnumerable<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 laList<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:La collection source implements
ICollection<T>
: ThenICollection<T>.Count
est 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 deICollection<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.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 nouveauList<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'appelToList()
copiera simplement le tableau source et l'enveloppera dans unList<T>
objet. Même les performances du deuxième boîtier ne sont pas un sujet de préoccupation pour les petites collections.la source
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! :)la source
ToList()
crée une nouvelle liste et y place les éléments, ce qui signifie qu'il y a un coût associé à l'actionToList()
. 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
la source
Ce sera aussi (in) efficace que de faire:
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 sicollection
est anIEnumerable<T>
, cela forcera l'exécution. Sicollection
est un tableau, une liste, etc., il devrait l'êtreO(1)
.S'il est
collection
implémentéICollection<T>
, il enregistrera les éléments dans un tableau interne à l'aide de laICollection<T>.CopyTo
méthode. Cela devrait êtreO(n)
, étantn
la longueur de la collection.Si elle
collection
n'est pas implémentéeICollection<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 cas
O(n)
, car il va parcourir lecollection
pour faire une copie de chaque élément.la source
0(n)
oùn
est 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)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.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 leIEnumerable<T>
(généralement une requête).la source
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
la source