Je rencontre souvent le cas où je veux évaluer une requête là où je la déclare. C'est généralement parce que j'ai besoin de répéter plusieurs fois et que le calcul est coûteux. Par exemple:
string raw = "...";
var lines = (from l in raw.Split('\n')
let ll = l.Trim()
where !string.IsNullOrEmpty(ll)
select ll).ToList();
Cela fonctionne bien. Mais si je ne vais pas modifier le résultat, je ferais aussi bien d'appeler à la ToArray()
place de ToList()
.
Je me demande cependant si ToArray()
est implémenté par le premier appel ToList()
et est donc moins efficace en mémoire que le simple appel ToList()
.
Suis-je fou? Dois-je simplement appelerToArray()
- sûr et sécurisé en sachant que la mémoire ne sera pas allouée deux fois?
.net
linq
performance
Frank Krueger
la source
la source
Réponses:
Sauf si vous avez simplement besoin d'un tableau pour répondre à d'autres contraintes que vous devez utiliser
ToList
. Dans la majorité des scénarios,ToArray
il allouera plus de mémoire queToList
.Les deux utilisent des tableaux pour le stockage, mais
ToList
ont une contrainte plus flexible. Il a besoin que le tableau soit au moins aussi grand que le nombre d'éléments dans la collection. Si le tableau est plus grand, ce n'est pas un problème. CependantToArray
, le tableau doit être dimensionné exactement au nombre d'éléments.Répondre à cette contrainte fait
ToArray
souvent une allocation de plus queToList
. Une fois qu'il a un tableau suffisamment grand, il alloue un tableau qui a exactement la bonne taille et copie les éléments dans ce tableau. La seule fois où il peut éviter cela, c'est lorsque l'algorithme de croissance du tableau coïncide avec le nombre d'éléments devant être stockés (certainement dans la minorité).ÉDITER
Quelques personnes m'ont interrogé sur les conséquences de la mémoire supplémentaire inutilisée dans le
List<T>
valeur.C'est une préoccupation valable. Si la collection créée a une longue durée de vie, n'est jamais modifiée après avoir été créée et a de grandes chances d'atterrir dans le tas Gen2, alors vous feriez mieux de prendre l'allocation supplémentaire de
ToArray
avance.En général, je trouve que c'est le cas le plus rare. Il est beaucoup plus courant de voir un grand nombre d'
ToArray
appels qui sont immédiatement transmis à d'autres utilisations de courte durée de la mémoire, auquel cas ilToList
est manifestement préférable.La clé ici est de profiler, profiler puis profiler un peu plus.
la source
ToArray
peut allouer plus de mémoire s'il a besoin de la taille exacte des emplacements oùToList<>
il y a évidemment ses emplacements de rechange automatiques. (autoLa différence de performance sera insignifiante, car
List<T>
est implémentée en tant que tableau de taille dynamique. Appelant soitToArray()
(qui utilise uneBuffer<T>
classe interne pour agrandir le tableau) soitToList()
(qui appelle leList<T>(IEnumerable<T>)
constructeur) finira par les placer dans un tableau et agrandir le tableau jusqu'à ce qu'il convienne à tous.Si vous souhaitez une confirmation concrète de ce fait, consultez la mise en œuvre des méthodes en question dans Reflector - vous verrez qu'elles se résument à un code presque identique.
la source
ToArray()
etToList()
est que le premier doit couper l'excédent, ce qui implique de copier l'ensemble du tableau, tandis que le second ne coupe pas l'excédent, mais utilise une moyenne de 25 % plus de mémoire. Cela n'aura des implications que si le type de données est grandstruct
. Juste matière à réflexion.ToList
ouToArray
commencera par la création d'un petit tampon. Lorsque ce tampon est rempli, il double la capacité du tampon et continue. La capacité étant toujours doublée, le tampon inutilisé sera toujours compris entre 0% et 50%.List
etBuffer
vérifieraICollection
, auquel cas les performances seront identiques.(sept ans plus tard ...)
Quelques autres (bonnes) réponses se sont concentrées sur les différences de performances microscopiques qui se produiront.
Cet article n'est qu'un complément pour mentionner la différence sémantique qui existe entre le
IEnumerator<T>
produit par un tableau (T[]
) et celui renvoyé par aList<T>
.Mieux illustré par l'exemple:
Le code ci-dessus s'exécutera sans exception et produira la sortie:
Cela montre que le
IEnumarator<int>
retourné par unint[]
ne garde pas de trace si le tableau a été modifié depuis la création de l'énumérateur.Notez que j'ai déclaré la variable locale en
source
tant queIList<int>
. De cette façon, je m'assure que le compilateur C # n'optimise pas l'foreach
instruction en quelque chose qui équivaut à unefor (var idx = 0; idx < source.Length; idx++) { /* ... */ }
boucle. C'est quelque chose que le compilateur C # pourrait faire si j'utilise à lavar source = ...;
place. Dans ma version actuelle du framework .NET, l'énumérateur réel utilisé ici est un type de référence non public,System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]
mais bien sûr, c'est un détail d'implémentation.Maintenant, si je change
.ToArray()
en.ToList()
, je reçois seulement:suivi d'un
System.InvalidOperationException
éclatement disant:L'énumérateur sous-jacent dans ce cas est le type de valeur mutable public
System.Collections.Generic.List`1+Enumerator[System.Int32]
(encadré dans uneIEnumerator<int>
boîte dans ce cas parce que j'utiliseIList<int>
).En conclusion, l'énumérateur produit par un
List<T>
garde la trace de la modification de la liste pendant l'énumération, alors que l'énumérateur produit parT[]
ne le fait pas. Considérez donc cette différence lors du choix entre.ToList()
et.ToArray()
.Les gens en ajoutent souvent un supplémentaire
.ToArray()
ou.ToList()
contournent une collection qui garde la trace de sa modification pendant la durée de vie d'un enquêteur.(Si quelqu'un veut savoir comment le
List<>
garde trace si la collection a été modifiée, il y a un champ privé_version
dans cette classe qui est changé à chaqueList<>
mise à jour.)la source
Je suis d'accord avec @mquander que la différence de performances devrait être insignifiante. Cependant, je voulais le comparer pour être sûr, alors je l'ai fait - et c'est insignifiant.
Chaque tableau / liste source avait 1 000 éléments. Vous pouvez donc voir que les différences de temps et de mémoire sont négligeables.
Ma conclusion: vous pourriez aussi bien utiliser ToList () , car a
List<T>
fournit plus de fonctionnalités qu'un tableau, à moins que quelques octets de mémoire ne vous importent vraiment.la source
struct
au lieu d'un type ou d'une classe primitive.ToList
ouToArray
appel et non l'énumération de toutIEnumerable
. List <T> .ToList () crée toujours une nouvelle List <T> - elle ne se contente pas de "retourner ceci".ToArray()
etToList()
diffèrent trop lorsqu'ils sont fournis avec unICollection<T>
paramètre - Ils ne font qu'une seule allocation et une seule opération de copie. Les deuxList<T>
etArray
implémententICollection<T>
, donc vos repères ne sont pas du tout valides..Select(i => i)
pour éviter leICollection<T>
problème d'implémentation et inclut un groupe de contrôle pour voir combien de temps est simplement pris en itérant sur la sourceIEnumerable<>
en premier lieu.ToList()
est généralement préférable si vous l'utilisezIEnumerable<T>
(depuis ORM, par exemple). Si la longueur de la séquence n'est pas connue au début,ToArray()
crée une collection de longueur dynamique comme List, puis la convertit en tableau, ce qui prend plus de temps.la source
Enumerable.ToArray()
appelsnew Buffer<TSource>(source).ToArray()
. Dans le constructeur Buffer si la source implémente ICollection, elle appelle alors source.CopyTo (items, 0), puis .ToArray () renvoie directement le tableau des éléments internes. Il n'y a donc aucune conversion qui prend du temps supplémentaire dans ce cas. Si la source n'implémente pas ICollection, le ToArray entraînera une copie du tableau afin de supprimer les emplacements supplémentaires inutilisés à la fin du tableau, comme décrit par le commentaire de Scott Rippey ci-dessus.La mémoire sera toujours allouée deux fois - ou quelque chose de proche. Comme vous ne pouvez pas redimensionner un tableau, les deux méthodes utiliseront une sorte de mécanisme pour collecter les données dans une collection croissante. (Eh bien, la Liste est une collection croissante en soi.)
La liste utilise une baie comme stockage interne et double la capacité en cas de besoin. Cela signifie qu'en moyenne 2/3 des articles ont été réalloués au moins une fois, la moitié de ceux réalloués au moins deux fois, la moitié de ceux au moins trois fois, et ainsi de suite. Cela signifie que chaque élément a été en moyenne réalloué 1,3 fois, ce qui n'est pas beaucoup de frais généraux.
N'oubliez pas également que si vous collectez des chaînes, la collection elle-même ne contient que les références aux chaînes, les chaînes elles-mêmes ne sont pas réallouées.
la source
Nous sommes en 2020 à l'extérieur et tout le monde utilise .NET Core 3.1, j'ai donc décidé d'exécuter des benchmarks avec Benchmark.NET.
TL; DR: ToArray () est meilleur en termes de performances et fait une meilleure intention de transmission de travail si vous ne prévoyez pas de muter la collection.
Les résultats sont:
la source
ToImmutableArray()
(à partir du package System.Collections.Immutable) 😉Éditer : La dernière partie de cette réponse n'est pas valide. Cependant, le reste est toujours une information utile, donc je vais le laisser.
Je sais que c'est un vieux post, mais après avoir posé la même question et fait quelques recherches, j'ai trouvé quelque chose d'intéressant qui pourrait valoir la peine d'être partagé.
Tout d'abord, je suis d'accord avec @mquander et sa réponse. Il a raison de dire que sur le plan des performances, les deux sont identiques.
Cependant, j'ai utilisé Reflector pour jeter un œil aux méthodes dans l'
System.Linq.Enumerable
espace de noms des extensions, et j'ai remarqué une optimisation très courante.Dans la mesure du possible, la
IEnumerable<T>
source est convertie enIList<T>
ouICollection<T>
pour optimiser la méthode. Par exemple, regardezElementAt(int)
.Fait intéressant, Microsoft a choisi d'optimiser uniquement pour
IList<T>
, mais pasIList
. Il semble que Microsoft préfère utiliser l'IList<T>
interface.System.Array
implémente uniquementIList
, il ne bénéficiera donc d'aucune de ces optimisations d'extension.Par conséquent, je soumets que la meilleure pratique consiste à utiliser la
.ToList()
méthode.Si vous utilisez l'une des méthodes d'extension ou passez la liste à une autre méthode, il est possible qu'elle soit optimisée pour un
IList<T>
.la source
J'ai trouvé que les autres repères que les gens ont faits ici manquaient, alors voici ma fissure. Faites-moi savoir si vous trouvez quelque chose de mal avec ma méthodologie.
Vous pouvez télécharger le script LINQPad ici .
Résultats:
En ajustant le code ci-dessus, vous découvrirez que:
int
s plutôt que destring
art.struct
s au lieu destring
s prend généralement beaucoup plus de temps, mais ne change pas vraiment le rapport.Cela rejoint les conclusions des réponses les mieux notées:
ToList()
fonctionne toujours plus rapidement et serait un meilleur choix si vous ne prévoyez pas de vous accrocher aux résultats pendant longtemps.Mise à jour
@JonHanna a souligné qu'en fonction de l'implémentation de,
Select
il est possible pour aToList()
ou l'ToArray()
implémentation de prédire à l'avance la taille de la collection résultante. Remplacer.Select(i => i)
dans le code ci-dessus avecWhere(i => true)
des résultats très similaires pour le moment, et est plus susceptible de le faire quelle que soit l'implémentation .NET.la source
100000
et l'utilisera pour optimiser les deuxToList()
etToArray()
,ToArray()
étant très légèrement plus léger car il n'a pas besoin de l'opération de réduction dont il aurait besoin sinon, qui est le seul endroitToList()
a l'avantage. L'exemple dans la question serait toujours perdu, car lesWhere
moyens d'une telle prédiction de taille ne peuvent pas être faits..Select(i => i)
pourrait être remplacé par.Where(i => true)
pour corriger cela.ToArray()
un avantage) et une autre qui ne l'est pas, comme ci-dessus, et de comparer les résultats.ToArray()
perd toujours dans le meilleur des cas. Avec lesMath.Pow(2, 15)
éléments, c'est (ToList: 700ms, ToArray: 900ms). L'ajout d'un élément supplémentaire le renverse (ToList: 925, ToArray: 1350). Je me demande siToArray
est toujours en train de copier la matrice même si elle est déjà à la taille parfaite? Ils ont probablement pensé que c'était un événement assez rare pour que cela ne valait pas la peine supplémentaire conditionnelle.Vous devez baser votre décision sur
ToList
ou enToArray
fonction de ce que, idéalement, le choix de conception est. Si vous voulez une collection qui ne peut être itérée et accessible que par index, choisissezToArray
. Si vous souhaitez des fonctionnalités supplémentaires d'ajout et de suppression de la collection plus tard sans trop de tracas, faites unToList
(pas vraiment que vous ne pouvez pas ajouter à un tableau, mais ce n'est généralement pas le bon outil pour cela).Si les performances sont importantes, vous devez également envisager ce qui serait plus rapide à utiliser. De façon réaliste, vous n'appelerez pas
ToList
ouToArray
un million de fois, mais vous pourriez travailler sur la collection obtenue un million de fois. À cet égard,[]
c'est mieux, carList<>
c'est[]
avec des frais généraux. Voir ce fil pour une comparaison d'efficacité: Lequel est le plus efficace: List <int> or int []Dans mes propres tests il y a quelque temps, j'avais trouvé
ToArray
plus vite. Et je ne sais pas à quel point les tests étaient biaisés. La différence de performances est cependant insignifiante, ce qui ne peut être perceptible que si vous exécutez ces requêtes dans une boucle des millions de fois.la source
Une réponse très tardive mais je pense que ce sera utile pour les googleurs.
Ils sucent tous les deux lorsqu'ils ont créé avec linq. Ils implémentent tous les deux le même code pour redimensionner le tampon si nécessaire .
ToArray
utilise en interne une classe pour convertirIEnumerable<>
en tableau, en allouant un tableau de 4 éléments. Si cela ne suffit pas, cela double la taille en créant un nouveau tableau double la taille du tableau actuel et en copiant le tableau actuel. À la fin, il alloue un nouveau tableau de comptage de vos articles. Si votre requête retourne 129 éléments, ToArray effectuera 6 allocations et opérations de copie de mémoire pour créer un tableau de 256 éléments et puis un autre tableau de 129 pour retourner. tant pour l'efficacité de la mémoire.ToList fait la même chose, mais il ignore la dernière allocation puisque vous pouvez ajouter des éléments à l'avenir. La liste ne se soucie pas si elle est créée à partir d'une requête linq ou créée manuellement.
pour la création List est meilleure avec la mémoire mais pire avec cpu car list est une solution générique chaque action nécessite des vérifications de plage supplémentaires aux vérifications de plage interne du .net pour les tableaux.
Donc, si vous parcourez votre jeu de résultats trop de fois, les tableaux sont bons, car cela signifie moins de vérifications de plage que les listes, et les compilateurs optimisent généralement les tableaux pour un accès séquentiel.
L'allocation d'initialisation de la liste peut être meilleure si vous spécifiez le paramètre de capacité lorsque vous le créez. Dans ce cas, il n'allouera le tableau qu'une seule fois, en supposant que vous connaissez la taille du résultat.
ToList
de linq ne spécifie pas de surcharge pour le fournir, nous devons donc créer notre méthode d'extension qui crée une liste avec une capacité donnée, puis utiliseList<>.AddRange
.Pour terminer cette réponse, je dois écrire les phrases suivantes
la source
List<T>
, mais quand vous ne le faites pas ou quand vous ne le pouvez pas, vous ne pouvez pas l'aider.C'est une vieille question - mais pour le bénéfice des utilisateurs qui y tombent, il existe également une alternative à la `` mémorisation '' de l'énumérable - qui a pour effet de mettre en cache et d'arrêter l'énumération multiple d'une instruction Linq, ce que ToArray () et ToList () sont beaucoup utilisés, même si les attributs de collection de la liste ou du tableau ne sont jamais utilisés.
Memoize est disponible dans la bibliothèque RX / System.Interactive et est expliqué ici: Plus de LINQ avec System.Interactive
(Du blog de Bart De'Smet qui est une lecture fortement recommandée si vous travaillez beaucoup avec Linq to Objects)
la source
Une option consiste à ajouter votre propre méthode d'extension qui renvoie une lecture seule
ICollection<T>
. Cela peut être mieux que d'utiliserToList
ouToArray
lorsque vous ne souhaitez pas utiliser les propriétés d'indexation d'un tableau / liste ou ajouter / supprimer d'une liste.Tests unitaires:
la source
ToListAsync<T>()
est préféré.Dans Entity Framework 6, les deux méthodes finissent par appeler la même méthode interne, mais les
ToArrayAsync<T>()
appelslist.ToArray()
à la fin, qui sont implémentés commeIl en
ToArrayAsync<T>()
est de même pour certains frais généraux, ce quiToListAsync<T>()
est préférable.la source
Vieille question mais nouveaux questionneurs en tout temps.
Selon la source de System.Linq.Enumerable , il
ToList
suffit de renvoyer anew List(source)
, tout enToArray
utilisant anew Buffer<T>(source).ToArray()
pour renvoyer aT[]
.Lors de l'exécution sur un
IEnumerable<T>
seul objet,ToArray
allouez de la mémoire une fois de plusToList
. Mais vous n'avez pas à vous en soucier dans la plupart des cas, car GC effectuera la collecte des ordures en cas de besoin.Ceux qui posent cette question peuvent exécuter le code suivant sur votre propre machine et vous obtiendrez votre réponse.
J'ai obtenu ces résultats sur ma machine:
En raison de la limite de stackoverflow au nombre de caractères de la réponse, les exemples de listes de Group2 et Group3 sont omis.
Comme vous pouvez le voir, ce n'est vraiment pas important à utiliser
ToList
ouToArry
dans la plupart des cas.Lors du traitement des
IEnumerable<T>
objets calculés à l'exécution , si la charge apportée par le calcul est plus lourde que les opérations d'allocation de mémoire et de copie deToList
etToArray
, la disparité est insignifiante (C.ToList vs C.ToArray
etS.ToList vs S.ToArray
).La différence ne peut être observée que sur des
IEnumerable<T>
objets uniquement non calculés lors de l'exécution (C1.ToList vs C1.ToArray
etS1.ToList vs S1.ToArray
). Mais la différence absolue (<60 ms) est toujours acceptable sur un million de petits objetsIEnumerable<T>
. En fait, la différence est décidée par la mise en œuvreEnumerator<T>
deIEnumerable<T>
. Donc, si votre programme est vraiment vraiment très sensible à cela, vous devez profiler, profiler, profiler ! Enfin, vous constaterez probablement que le goulot d'étranglement n'est pas surToList
ouToArray
, mais le détail des énumérateurs.Et le résultat de
C2.ToList vs C2.ToArray
etS2.ToList vs S2.ToArray
montre que, vous ne vraiment pas besoin de prendre soinToList
ouToArray
sur la non-exécution calculéeICollection<T>
objets.Bien sûr, ce ne sont que des résultats sur ma machine, le temps réel consacré à ces opérations sur une machine différente ne sera pas le même, vous pouvez le découvrir sur votre machine en utilisant le code ci-dessus.
La seule raison pour laquelle vous devez faire un choix est que vous avez des besoins spécifiques sur
List<T>
ouT[]
, comme décrit par la réponse de @Jeppe Stig Nielsen .la source
Pour toute personne intéressée à utiliser ce résultat dans un autre Linq-to-sql tel que
le SQL généré est le même, que vous ayez utilisé une liste ou un tableau pour myListOrArray. Maintenant, je sais que certains peuvent demander pourquoi même énumérer avant cette déclaration, mais il existe une différence entre le SQL généré à partir d'un IQueryable vs (List ou Array).
la source