Ich stoße oft auf den Fall, dass ich eine Abfrage genau dort bewerten möchte, wo ich sie deklariere. Dies liegt normalerweise daran, dass ich mehrmals darüber iterieren muss und die Berechnung teuer ist. Zum Beispiel:
string raw = "...";
var lines = (from l in raw.Split('\n')
let ll = l.Trim()
where !string.IsNullOrEmpty(ll)
select ll).ToList();
Das funktioniert gut. Aber wenn ich das Ergebnis nicht gehen zu ändern, dann könnte ich auch nennen ToArray()
statt ToList()
.
Ich frage mich jedoch, ob dies ToArray()
beim ersten Aufruf implementiert wird ToList()
und daher weniger speichereffizient ist als nur das Aufrufen ToList()
.
Bin ich verrückt? Sollte ich einfach anrufen ToArray()
- sicher und sicher in dem Wissen, dass der Speicher nicht zweimal zugewiesen wird?
.net
linq
performance
Frank Krueger
quelle
quelle
Antworten:
Es sei denn, Sie benötigen lediglich ein Array, um andere Einschränkungen zu erfüllen, die Sie verwenden sollten
ToList
. In den meisten SzenarienToArray
wird mehr Speicher zugewiesen alsToList
.Beide verwenden Arrays für die Speicherung, haben jedoch
ToList
eine flexiblere Einschränkung. Das Array muss mindestens so groß sein wie die Anzahl der Elemente in der Sammlung. Wenn das Array größer ist, ist das kein Problem. DasToArray
Array muss jedoch genau auf die Anzahl der Elemente zugeschnitten sein.Um diese Einschränkung zu erfüllen, muss
ToArray
häufig mehr zugewiesen werden alsToList
. Sobald es ein Array hat, das groß genug ist, weist es ein Array zu, das genau die richtige Größe hat, und kopiert die Elemente zurück in dieses Array. Dies kann nur vermieden werden, wenn der Wachstumsalgorithmus für das Array zufällig mit der Anzahl der Elemente übereinstimmt, die gespeichert werden müssen (definitiv in der Minderheit).BEARBEITEN
Ein paar Leute haben mich nach der Konsequenz gefragt, dass der zusätzliche ungenutzte Speicher im
List<T>
Wert enthalten ist.Dies ist ein berechtigtes Anliegen. Wenn die erstellte Sammlung langlebig ist, nach ihrer
ToArray
Erstellung nie geändert wird und eine hohe Wahrscheinlichkeit besteht, im Gen2-Heap zu landen, ist es möglicherweise besser, die zusätzliche Zuweisung von vornherein zu übernehmen.Im Allgemeinen finde ich dies jedoch der seltenere Fall. Es kommt viel häufiger vor, dass viele
ToArray
Anrufe sofort an andere kurzlebige Speichernutzungen weitergeleitet werden. In diesem FallToList
ist dies nachweislich besser.Der Schlüssel hier ist, ein Profil zu erstellen, ein Profil zu erstellen und dann ein weiteres Profil zu erstellen.
quelle
ToArray
mehr Speicher zugewiesen werden kann, wenn die genaue Speicherortgröße benötigt wird, woToList<>
offensichtlich automatische Ersatzspeicherorte vorhanden sind. (Autoincrease)Der Leistungsunterschied ist unbedeutend, da er
List<T>
als dynamisch dimensioniertes Array implementiert ist. Wenn Sie entwederToArray()
(das eine interneBuffer<T>
Klasse zum Erweitern des Arrays verwendet) oderToList()
(das denList<T>(IEnumerable<T>)
Konstruktor aufruft ) aufrufen, müssen Sie sie in ein Array einfügen und das Array vergrößern, bis es allen passt.Wenn Sie eine konkrete Bestätigung dieser Tatsache wünschen, lesen Sie die Implementierung der fraglichen Methoden in Reflector - Sie werden sehen, dass sie sich auf nahezu identischen Code beschränken.
quelle
ToArray()
undToList()
darin, dass erstere den Überschuss kürzen müssen, was das Kopieren des gesamten Arrays beinhaltet, während letztere den Überschuss nicht kürzen, sondern einen Durchschnitt von 25 verwenden % Mehr Speicherplatz. Dies hat nur Auswirkungen, wenn der Datentyp groß iststruct
. Nur Denkanstöße.ToList
oderToArray
Erstellen eines kleinen Puffers begonnen. Wenn dieser Puffer gefüllt ist, verdoppelt er die Kapazität des Puffers und fährt fort. Da die Kapazität immer verdoppelt wird, liegt der nicht verwendete Puffer immer zwischen 0% und 50%.List
undBuffer
prüfenICollection
, in welchem Fall die Leistung identisch ist.(Sieben Jahre später...)
Einige andere (gute) Antworten haben sich auf mikroskopische Leistungsunterschiede konzentriert, die auftreten werden.
Dieser Beitrag ist nur eine Ergänzung, um den semantischen Unterschied zu erwähnen, der zwischen dem
IEnumerator<T>
von einem Array (T[]
) erzeugten und dem von a zurückgegebenen bestehtList<T>
.Am besten anhand eines Beispiels:
Der obige Code wird ohne Ausnahme ausgeführt und erzeugt die Ausgabe:
Dies zeigt, dass das
IEnumarator<int>
von einem zurückgegebeneint[]
nicht nachverfolgt, ob das Array seit der Erstellung des Enumerators geändert wurde.Beachten Sie, dass ich die lokale Variable
source
als deklariert habeIList<int>
. Auf diese Weise stelle ich sicher, dass der C # -Compiler dieforeach
Anweisung nicht in etwas optimiert, das einerfor (var idx = 0; idx < source.Length; idx++) { /* ... */ }
Schleife entspricht. Dies könnte der C # -Compiler tun, wenn ichvar source = ...;
stattdessen verwende. In meiner aktuellen Version des .NET Frameworks ist der hier verwendete Enumerator ein nicht öffentlicher Referenztyp,System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]
aber dies ist natürlich ein Implementierungsdetail.Nun, wenn ich ändern
.ToArray()
in.ToList()
, erhalte ich nur:gefolgt von einem
System.InvalidOperationException
Sprengsatz:Der zugrunde liegende Enumerator ist in diesem Fall der öffentliche veränderbare Werttyp
System.Collections.Generic.List`1+Enumerator[System.Int32]
(IEnumerator<int>
in diesem Fall in einem Feld, weil ich ihn verwendeIList<int>
).Zusammenfassend verfolgt der von a erzeugte
List<T>
Enumerator, ob sich die Liste während der Aufzählung ändert, während der von einem erzeugte EnumeratorT[]
dies nicht tut. Berücksichtigen Sie diesen Unterschied bei der Auswahl zwischen.ToList()
und.ToArray()
.Oft wird eine zusätzliche hinzugefügt
.ToArray()
oder.ToList()
um eine Sammlung zu umgehen, die nachverfolgt, ob sie während der Lebensdauer eines Enumerators geändert wurde.(Wenn jemand wissen will , wie die
List<>
verfolgt , ob Sammlung geändert wurde, gibt es einen privaten Bereich_version
in dieser Klasse , die jedes Mal geändert wird , dieList<>
aktualisiert wird.)quelle
Ich stimme @mquander zu, dass der Leistungsunterschied unbedeutend sein sollte. Ich wollte es jedoch messen, um sicherzugehen, also habe ich es getan - und es ist unbedeutend.
Jedes Quellarray / jede Quellliste hatte 1000 Elemente. Sie sehen also, dass sowohl Zeit- als auch Speicherunterschiede vernachlässigbar sind.
Mein Fazit: Sie können auch ToList () verwenden , da a
List<T>
mehr Funktionen als ein Array bietet, es sei denn, ein paar Bytes Speicher sind für Sie wirklich wichtig.quelle
struct
anstelle eines primitiven Typs oder einer primitiven Klasse verwenden würden.ToList
oder desToArray
Anrufs und nicht die Aufzählung von irgendwelchen messeIEnumerable
. List <T> .ToList () erstellt immer noch eine neue List <T> - es wird nicht einfach "zurückgegeben".ToArray()
undToList()
unterscheidet sich zu stark, wenn sie mit einemICollection<T>
Parameter geliefert werden - Sie führen nur eine einzelne Zuordnung und einen einzelnen Kopiervorgang durch. BeidesList<T>
undArray
implementierenICollection<T>
, sodass Ihre Benchmarks überhaupt nicht gültig sind..Select(i => i)
, um dasICollection<T>
Implementierungsproblem zu vermeiden , und enthält eine Kontrollgruppe, um zu sehen, wie viel Zeit nur für die Iteration über die Quelle benötigt wirdIEnumerable<>
.ToList()
wird normalerweise bevorzugt, wenn Sie es verwendenIEnumerable<T>
(z. B. von ORM). Wenn die Länge der Sequenz zu Beginn nicht bekannt ist, wirdToArray()
eine Sammlung mit dynamischer Länge wie List erstellt und anschließend in ein Array konvertiert, was zusätzliche Zeit in Anspruch nimmt.quelle
Enumerable.ToArray()
Anrufenew Buffer<TSource>(source).ToArray()
. Wenn die Quelle im Pufferkonstruktor ICollection implementiert, ruft sie source.CopyTo (items, 0) auf, und dann gibt .ToArray () das Array der internen Elemente direkt zurück. In diesem Fall ist keine Konvertierung erforderlich, die zusätzliche Zeit in Anspruch nimmt. Wenn die Quelle ICollection nicht implementiert, führt das ToArray zu einer Array-Kopie, um die zusätzlichen nicht verwendeten Positionen vom Ende des Arrays zu entfernen, wie in Scott Rippeys Kommentar oben beschrieben.Der Speicher wird immer zweimal zugewiesen - oder etwas in der Nähe davon. Da Sie die Größe eines Arrays nicht ändern können, verwenden beide Methoden einen Mechanismus, um die Daten in einer wachsenden Sammlung zu erfassen. (Nun, die Liste ist eine wachsende Sammlung für sich.)
Die Liste verwendet ein Array als internen Speicher und verdoppelt bei Bedarf die Kapazität. Dies bedeutet, dass durchschnittlich 2/3 der Elemente mindestens einmal neu zugewiesen wurden, die Hälfte der Elemente mindestens zweimal, die Hälfte mindestens dreimal und so weiter. Das bedeutet, dass jeder Artikel im Durchschnitt 1,3-mal neu zugewiesen wurde, was nicht sehr viel Aufwand bedeutet.
Denken Sie auch daran, dass beim Sammeln von Zeichenfolgen die Sammlung selbst nur die Verweise auf die Zeichenfolgen enthält und die Zeichenfolgen selbst nicht neu zugewiesen werden.
quelle
Es ist 2020 draußen und jeder verwendet .NET Core 3.1, daher habe ich beschlossen, einige Benchmarks mit Benchmark.NET durchzuführen.
TL; DR: ToArray () ist leistungsmäßig besser und vermittelt die Absicht besser, wenn Sie nicht vorhaben, die Sammlung zu mutieren.
Die Ergebnisse sind:
quelle
ToImmutableArray()
(aus dem System.Collections.Immutable-Paket) gezeigt werden. 😉Bearbeiten : Der letzte Teil dieser Antwort ist ungültig. Der Rest sind jedoch immer noch nützliche Informationen, daher lasse ich sie.
Ich weiß, dass dies ein alter Beitrag ist, aber nachdem ich die gleiche Frage gestellt und einige Nachforschungen angestellt habe, habe ich etwas Interessantes gefunden, das es wert sein könnte, geteilt zu werden.
Erstens stimme ich @mquander und seiner Antwort zu. Er sagt zu Recht, dass die beiden in Bezug auf die Leistung identisch sind.
Ich habe jedoch Reflector verwendet, um einen Blick auf die Methoden im
System.Linq.Enumerable
Erweiterungs-Namespace zu werfen , und ich habe eine sehr häufige Optimierung festgestellt.Wann immer möglich, wird die
IEnumerable<T>
Quelle in die Methode umgewandeltIList<T>
oderICollection<T>
optimiert. Schauen Sie sich zum Beispiel anElementAt(int)
.Interessanterweise hat Microsoft nur optimiert
IList<T>
, aber nichtIList
. Microsoft bevorzugt anscheinend die Verwendung derIList<T>
Benutzeroberfläche.System.Array
Nur implementiertIList
, daher wird es von keiner dieser Erweiterungsoptimierungen profitieren.Daher behaupte ich, dass die beste Vorgehensweise darin besteht, die
.ToList()
Methode zu verwenden.Wenn Sie eine der Erweiterungsmethoden verwenden oder die Liste an eine andere Methode übergeben, besteht die Möglichkeit, dass sie für eine optimiert wird
IList<T>
.quelle
Ich fand, dass die anderen Benchmarks, die die Leute hier gemacht haben, fehlen, also hier ist mein Knaller. Lassen Sie mich wissen, wenn Sie etwas mit meiner Methodik falsch finden.
Sie können das LINQPad-Skript hier herunterladen .
Ergebnisse:
Wenn Sie den obigen Code anpassen, werden Sie Folgendes feststellen:
int
s als umstring
s geht.struct
s anstelle vonstring
s nimmt im Allgemeinen viel mehr Zeit in Anspruch, ändert jedoch das Verhältnis nicht wesentlich.Dies stimmt mit den Schlussfolgerungen der am besten bewerteten Antworten überein:
ToList()
Läuft durchweg schneller und ist eine bessere Wahl, wenn Sie nicht vorhaben, lange an den Ergebnissen festzuhalten.Aktualisieren
@JonHanna wies darauf hin, dass es abhängig von der Implementierung
Select
einerToList()
oderToArray()
Implementierung möglich ist , die Größe der resultierenden Sammlung im Voraus vorherzusagen. Das Ersetzen.Select(i => i)
des obigen Codes durch CodeWhere(i => true)
führt derzeit zu sehr ähnlichen Ergebnissen , und dies ist wahrscheinlicher, unabhängig von der .NET-Implementierung.quelle
100000
und dies verwendet, um beide zu optimieren,ToList()
undToArray()
mitToArray()
etwas weniger Leichtigkeit, weil es nicht den Schrumpfvorgang benötigt, den es benötigen würde Ansonsten hat der eine OrtToList()
den Vorteil. Das Beispiel in der Frage würde immer noch verlieren, da dieWhere
Mittel einer solchen Größenvorhersage nicht durchgeführt werden können..Select(i => i)
könnte durch ersetzt werden, um dies.Where(i => true)
zu korrigieren.ToArray()
einen Vorteil bringen sollte), als auch eine, die nicht wie oben ist, und die Ergebnisse zu vergleichen.ToArray()
verliert immer noch im besten Fall. MitMath.Pow(2, 15)
Elementen ist es (ToList: 700 ms, ToArray: 900 ms). Durch Hinzufügen eines weiteren Elements wird es erhöht (ToList: 925, ToArray: 1350). Ich frage mich, obToArray
das Array noch kopiert wird, auch wenn es bereits die perfekte Größe hat. Sie dachten wahrscheinlich, dass es selten genug war, dass es die zusätzliche Bedingung nicht wert war.Sie sollten Ihre Entscheidung für
ToList
oderToArray
basierend auf der idealen Designauswahl treffen . Wenn Sie eine Sammlung wünschen, auf die nur durch Index iteriert und zugegriffen werden kann, wählen SieToArray
. Wenn Sie später ohne großen Aufwand zusätzliche Funktionen zum Hinzufügen und Entfernen aus der Sammlung benötigen, führen Sie eine ausToList
(nicht wirklich, dass Sie einem Array nicht hinzufügen können, aber das ist normalerweise nicht das richtige Werkzeug dafür).Wenn es auf die Leistung ankommt, sollten Sie auch überlegen, was schneller zu bedienen wäre. Realistisch gesehen werden Sie nicht
ToList
oderToArray
millionenfach anrufen , aber möglicherweise millionenfach an der erhaltenen Sammlung arbeiten. In dieser Hinsicht[]
ist besser, daList<>
ist[]
mit einem gewissen Aufwand. In diesem Thread finden Sie einen Effizienzvergleich: Welcher ist effizienter: List <int> oder int []In meinen eigenen Tests vor einiger Zeit hatte ich
ToArray
schneller gefunden . Und ich bin mir nicht sicher, wie schief die Tests waren. Der Leistungsunterschied ist jedoch so gering, dass er sich nur bemerkbar macht, wenn Sie diese Abfragen millionenfach in einer Schleife ausführen.quelle
Eine sehr späte Antwort, aber ich denke, sie wird für Googler hilfreich sein.
Sie saugen beide, wenn sie mit linq erstellt haben. Beide implementieren denselben Code, um bei Bedarf die Größe des Puffers zu ändern .
ToArray
Verwendet intern eine Klasse zum KonvertierenIEnumerable<>
in ein Array, indem ein Array mit 4 Elementen zugewiesen wird. Wenn dies nicht ausreicht, verdoppelt sich die Größe, indem ein neues Array erstellt wird, das die Größe des aktuellen Arrays verdoppelt und das aktuelle Array darauf kopiert. Am Ende wird ein neues Array der Anzahl Ihrer Artikel zugewiesen. Wenn Ihre Abfrage 129 Elemente zurückgibt, führt ToArray 6 Zuweisungen und Speicherkopiervorgänge durch, um ein Array mit 256 Elementen zu erstellen, und anschließend ein weiteres Array mit 129 Elementen. Soviel zur Speichereffizienz.ToList macht dasselbe, überspringt jedoch die letzte Zuordnung, da Sie in Zukunft Elemente hinzufügen können. List ist es egal, ob es aus einer Linq-Abfrage erstellt oder manuell erstellt wird.
für die Erstellung Liste ist besser mit Speicher, aber schlechter mit CPU, da Liste eine generische Lösung ist. Jede Aktion erfordert Bereichsprüfungen zusätzlich zu den internen Bereichsprüfungen des .net für Arrays.
Wenn Sie also Ihre Ergebnismenge zu oft durchlaufen, sind Arrays gut, da dies weniger Bereichsprüfungen als Listen bedeutet und Compiler Arrays im Allgemeinen für den sequentiellen Zugriff optimieren.
Die Initialisierungszuordnung der Liste kann besser sein, wenn Sie beim Erstellen Kapazitätsparameter angeben. In diesem Fall wird das Array nur einmal zugewiesen, vorausgesetzt, Sie kennen die Ergebnisgröße.
ToList
of linq gibt keine Überladung an, um sie bereitzustellen. Daher müssen wir unsere Erweiterungsmethode erstellen, die eine Liste mit der angegebenen Kapazität erstellt und dann verwendetList<>.AddRange
.Um diese Antwort zu beenden, muss ich folgende Sätze schreiben
quelle
List<T>
, aber wenn Sie dies nicht tun oder wenn Sie es nicht können, können Sie nichts dagegen tun.Dies ist eine alte Frage - aber zum Nutzen der Benutzer, die darauf stoßen, gibt es auch eine Alternative zum "Auswendiglernen" der Aufzählung -, die dazu führt, dass eine Linq-Anweisung mehrfach zwischengespeichert und gestoppt wird, was ToArray () ist. und ToList () werden häufig verwendet, obwohl die Auflistungsattribute der Liste oder des Arrays niemals verwendet werden.
Memoize ist in der Bibliothek RX / System.Interactive verfügbar und wird hier erklärt: Mehr LINQ mit System.Interactive
(Aus dem Blog von Bart De'Smet, der sehr zu empfehlen ist, wenn Sie viel mit Linq to Objects arbeiten.)
quelle
Eine Möglichkeit besteht darin, eine eigene Erweiterungsmethode hinzuzufügen, die schreibgeschützt zurückgibt
ICollection<T>
. Dies kann besser sein als die VerwendungToList
oderToArray
wenn Sie weder die Indizierungseigenschaften eines Arrays / einer Liste verwenden noch eine Liste hinzufügen / daraus entfernen möchten.Unit Tests:
quelle
ToListAsync<T>()
Ist bevorzugt.In Entity Framework 6 beide Methoden aufrufen schließlich zur gleichen internen Methode, aber
ToArrayAsync<T>()
Anrufelist.ToArray()
am Ende, die als umgesetzt wirdHat
ToArrayAsync<T>()
also einige Gemeinkosten, wird dabeiToListAsync<T>()
bevorzugt.quelle
Alte Frage, aber immer neue Fragesteller.
Nach Quelle System.Linq.Enumerable ,
ToList
Rückkehr nur einnew List(source)
, während dieToArray
Verwendung einernew Buffer<T>(source).ToArray()
ein zurückzukehrenT[]
.Weisen Sie beim Ausführen eines
IEnumerable<T>
einzigen ObjektsToArray
einmal mehr Speicher zu alsToList
. In den meisten Fällen müssen Sie sich jedoch nicht darum kümmern, da GC bei Bedarf die Speicherbereinigung durchführt.Diejenigen, die diese Frage stellen, können den folgenden Code auf Ihrem eigenen Computer ausführen, und Sie erhalten Ihre Antwort.
Ich habe diese Ergebnisse auf meiner Maschine:
Aufgrund der Beschränkung des Stapelüberlaufs auf die Zeichenmenge der Antwort werden die Beispiellisten von Gruppe2 und Gruppe3 weggelassen.
Wie Sie sehen können, ist es wirklich nicht wichtig,
ToList
oderToArry
in den meisten Fällen zu verwenden.IEnumerable<T>
Wenn bei der Verarbeitung von zur Laufzeit berechneten Objekten die durch die Berechnung verursachte Last höher ist als die Speicherzuweisung und die Kopiervorgänge vonToList
undToArray
, ist die Disparität unbedeutend (C.ToList vs C.ToArray
undS.ToList vs S.ToArray
).Der Unterschied kann nur bei nicht zur Laufzeit berechneten
IEnumerable<T>
Objekten (C1.ToList vs C1.ToArray
undS1.ToList vs S1.ToArray
) beobachtet werden. Die absolute Differenz (<60 ms) ist jedoch bei einer Million kleiner Objekte immer noch akzeptabelIEnumerable<T>
. In der Tat wird der Unterschied durch die ImplementierungEnumerator<T>
von entschiedenIEnumerable<T>
. Wenn Ihr Programm diesbezüglich wirklich sehr, sehr sensibel ist, müssen Sie ein Profil, ein Profil, ein Profil erstellen ! Endlich werden Sie wahrscheinlich feststellen, dass der Engpass nicht aufToList
oder liegtToArray
, sondern auf den Details der Enumeratoren.Und das Ergebnis von
C2.ToList vs C2.ToArray
undS2.ToList vs S2.ToArray
zeigt, dass Sie sich wirklich nicht darum kümmern müssenToList
oderToArray
nicht zur Laufzeit berechnet werdenICollection<T>
Objekte.Dies ist natürlich nur ein Ergebnis auf meinem Computer. Die tatsächliche Zeit, die diese Vorgänge auf verschiedenen Computern verbringen, ist nicht gleich. Sie können dies auf Ihrem Computer mithilfe des obigen Codes herausfinden.
Der einzige Grund, warum Sie eine Wahl treffen müssen, ist, dass Sie spezielle Anforderungen an
List<T>
oder habenT[]
, wie in der Antwort von @Jeppe Stig Nielsen beschrieben .quelle
Für alle, die daran interessiert sind, dieses Ergebnis in einem anderen Linq-to-SQL wie z
Dann ist die generierte SQL dieselbe, unabhängig davon, ob Sie eine Liste oder ein Array für myListOrArray verwendet haben. Jetzt weiß ich, dass einige fragen mögen, warum sie vor dieser Anweisung überhaupt aufzählen, aber es gibt einen Unterschied zwischen dem SQL, das aus einem IQueryable generiert wurde, vs (List oder Array).
quelle