Gibt es bei der Verwendung ToList()
Auswirkungen auf die Leistung, die berücksichtigt werden müssen?
Ich habe eine Abfrage geschrieben, um Dateien aus einem Verzeichnis abzurufen. Dies ist die Abfrage:
string[] imageArray = Directory.GetFiles(directory);
Da ich jedoch List<>
stattdessen gerne arbeite , habe ich mich entschlossen, ...
List<string> imageList = Directory.GetFiles(directory).ToList();
Gibt es also Auswirkungen auf die Leistung, die bei der Entscheidung für eine solche Konvertierung berücksichtigt werden sollten - oder nur bei der Verarbeitung einer großen Anzahl von Dateien? Ist das eine vernachlässigbare Umwandlung?
c#
arrays
performance
list
Cody
quelle
quelle
List<T>
für ein ,T[]
wenn es der Code logischer / lesbar / verwaltbar macht (es sei denn natürlich die Umwandlung wurde verursacht spürbare Performance - Probleme in diesem Fall ich würde re- besuche es, denke ich).Add
oderRemove
, würde ich es alsIEnumerable<T>
(oder noch besservar
)EnumerateFiles
stattGetFiles
, so dass nur ein Array erstellt wird.GetFiles(directory)
, wie es derzeit in .NET implementiert ist, tut es so ziemlichnew List<string>(EnumerateFiles(directory)).ToArray()
. SoGetFiles(directory).ToList()
erstellt eine Liste, erstellt einen Array aus , dass, erstellt dann wieder eine Liste. Wie 2kay sagt, sollten Sie es vorziehen,EnumerateFiles(directory).ToList()
hier zu tun .Antworten:
IEnumerable.ToList()
Ja,
IEnumerable<T>.ToList()
hat Auswirkungen auf die Leistung. Es handelt sich um eine O (n) -Operation, die jedoch wahrscheinlich nur bei leistungskritischen Operationen Beachtung erfordert.Die
ToList()
Operation verwendet denList(IEnumerable<T> collection)
Konstruktor. Dieser Konstruktor muss eine Kopie des Arrays erstellen (allgemeinerIEnumerable<T>
), da sich sonst zukünftige Änderungen des ursprünglichen Arrays auch an der Quelle ändern,T[]
was im Allgemeinen nicht wünschenswert wäre.Ich möchte noch einmal betonen, dass dies nur bei einer großen Liste einen Unterschied macht. Das Kopieren von Speicherblöcken ist eine recht schnelle Operation.
Handlicher Tipp,
As
vs.To
Sie werden feststellen, dass es in LINQ verschiedene Methoden gibt, die mit
As
(wieAsEnumerable()
) undTo
(wieToList()
) beginnen. Die Methoden, die mit beginnen,To
erfordern eine Konvertierung wie oben (dh sie können sich auf die Leistung auswirken), und die Methoden, die mit beginnen, erfordern und erfordern nur eine UmwandlungAs
oder eine einfache Operation.Zusätzliche Details zu
List<T>
Hier ist ein bisschen mehr Detail darüber, wie es
List<T>
funktioniert, falls Sie interessiert sind :)A
List<T>
verwendet auch ein Konstrukt, das als dynamisches Array bezeichnet wird und dessen Größe bei Bedarf geändert werden muss. Dieses Größenänderungsereignis kopiert den Inhalt eines alten Arrays in das neue Array. Es fängt also klein an und vergrößert sich bei Bedarf .Das ist der Unterschied zwischen dem
Capacity
undCount
Attribute aufList<T>
.Capacity
bezieht sich auf die Größe des Arrays hinter den Kulissen,Count
ist die Anzahl der Elemente in derList<T>
immer ist<= Capacity
. Wenn also ein Element zur Liste hinzugefügt wirdCapacity
, wird die Größe des ElementsList<T>
verdoppelt und das Array kopiert.quelle
List(IEnumerable<T> collection)
Konstruktor prüft, ob der Auflistungsparameter vorhanden ist,ICollection<T>
und dann sofort ein neues internes Array mit der erforderlichen Größe erstellt. Wenn dies nichtICollection<T>
der Fall ist, durchläuft der Konstruktor diese und ruftAdd
jedes Element auf.Ja natürlich. Theoretisch hat es sogar
i++
einen Einfluss auf die Leistung, es verlangsamt das Programm für vielleicht ein paar Ticks.Was macht
.ToList
dasWenn Sie aufrufen
.ToList
, ruft der Code auf ,Enumerable.ToList()
was eine Erweiterungsmethode ist, diereturn new List<TSource>(source)
. Im entsprechenden Konstruktor durchläuft es unter den schlimmsten Umständen den Elementcontainer und fügt sie einzeln in einen neuen Container ein. Daher wirkt sich sein Verhalten kaum auf die Leistung aus. Es ist unmöglich, ein leistungsfähiger Flaschenhals Ihrer Anwendung zu sein.Was ist los mit dem Code in der Frage
Directory.GetFiles
Wenn Sie den Ordner durchsuchen und alle Dateinamen sofort in den Speicher zurückgeben, besteht das potenzielle Risiko, dass die Zeichenfolge [] viel Speicher kostet und alles verlangsamt.Was ist dann zu tun?
Es hängt davon ab, ob. Wenn Sie (sowie Ihre Geschäftslogik) garantieren, dass die Dateimenge im Ordner immer klein ist, ist der Code akzeptabel. Es wird jedoch weiterhin empfohlen, eine faule Version zu verwenden:
Directory.EnumerateFiles
in C # 4. Dies ähnelt eher einer Abfrage, die nicht sofort ausgeführt wird. Sie können weitere Abfragen hinzufügen, z. B.:Dadurch wird die Suche nach dem Pfad beendet, sobald eine Datei gefunden wird, deren Name "myfile" enthält. Dies hat offensichtlich eine bessere Leistung als
.GetFiles
.quelle
Ja da ist. Mit der Erweiterungsmethode
Enumerable.ToList()
wird ein neuesList<T>
Objekt aus derIEnumerable<T>
Quellensammlung erstellt, was sich natürlich auf die Leistung auswirkt.Das Verständnis
List<T>
kann Ihnen jedoch dabei helfen, festzustellen, ob die Auswirkungen auf die Leistung erheblich sind.List<T>
verwendet ein Array (T[]
), um die Elemente der Liste zu speichern. Arrays können nicht erweitert werden, sobald sie zugewiesen wurden. DaherList<T>
wird ein übergroßes Array zum Speichern der Elemente der Liste verwendet. Wenn dasList<T>
zugrunde liegende Array über die Größe hinaus wächst, muss ein neues Array zugewiesen und der Inhalt des alten Arrays in das neue größere Array kopiert werden, bevor die Liste wachsen kann.Wenn ein neues
List<T>
aus einem erstellt wird,IEnumerable<T>
gibt es zwei Fälle:Die Quellensammlung implementiert
ICollection<T>
: AnschließendICollection<T>.Count
wird die genaue Größe der Quellensammlung ermittelt und ein passendes Sicherungsarray zugewiesen, bevor alle Elemente der Quellensammlung mit in das Sicherungsarray kopiert werdenICollection<T>.CopyTo()
. Diese Operation ist sehr effizient und wird wahrscheinlich einem CPU-Befehl zum Kopieren von Speicherblöcken zugeordnet. In Bezug auf die Leistung ist jedoch Speicher für das neue Array erforderlich, und zum Kopieren aller Elemente sind CPU-Zyklen erforderlich.Andernfalls ist die Größe der Quellensammlung unbekannt und der Enumerator von
IEnumerable<T>
wird verwendet, um jedes Quellelement einzeln zum neuen hinzuzufügenList<T>
. Zu Beginn ist das Hintergrundarray leer und ein Array der Größe 4 wird erstellt. Wenn dieses Array zu klein ist, wird die Größe verdoppelt, sodass das Hintergrundarray wie folgt wächst: 4, 8, 16, 32 usw. Jedes Mal, wenn das Hintergrundarray wächst, muss es neu zugewiesen und alle bisher gespeicherten Elemente müssen kopiert werden. Dieser Vorgang ist viel kostspieliger als der erste Fall, in dem sofort ein Array mit der richtigen Größe erstellt werden kann.Wenn Ihre Quellensammlung beispielsweise 33 Elemente enthält, verwendet die Liste ein Array von 64 Elementen, die Speicherplatz verschwenden.
In Ihrem Fall handelt es sich bei der Quellensammlung um ein Array, das implementiert wird,
ICollection<T>
sodass Sie sich keine Gedanken über die Auswirkungen auf die Leistung machen sollten, es sei denn, Ihr Quellarray ist sehr groß. Beim AufrufenToList()
wird einfach das Quellarray kopiert und in einList<T>
Objekt eingeschlossen. Auch die Leistung des zweiten Falles ist für kleine Sammlungen kein Grund zur Sorge.quelle
Das Problem mit Ihrem genauen Szenario ist, dass Ihre eigentliche Sorge um die Leistung in erster Linie von der Festplattengeschwindigkeit und der Effizienz des Laufwerkscaches abhängt.
Aus dieser Perspektive ist die Auswirkung sicherlich so vernachlässigbar, dass NEIN nicht berücksichtigt werden muss.
ABER NUR, wenn Sie die Funktionen der
List<>
Struktur wirklich benötigen , um möglicherweise produktiver, Ihren Algorithmus benutzerfreundlicher oder einen anderen Vorteil zu erzielen. Andernfalls fügen Sie absichtlich und ohne Grund einen unbedeutenden Leistungseinbruch hinzu. In diesem Fall sollten Sie es natürlich nicht tun! :) :)quelle
ToList()
erstellt eine neue Liste und fügt die Elemente darin ein, was bedeutet, dass damit verbundene Kosten verbunden sindToList()
. Im Falle einer kleinen Sammlung sind die Kosten nicht sehr spürbar, aber eine große Sammlung kann bei Verwendung von ToList zu Leistungseinbußen führen.Im Allgemeinen sollten Sie ToList () nicht verwenden, es sei denn, Ihre Arbeit kann nicht ausgeführt werden, ohne die Sammlung in List zu konvertieren. Wenn Sie beispielsweise nur die Sammlung durchlaufen möchten, müssen Sie keine ToList ausführen
Wenn Sie Abfragen für eine Datenquelle ausführen, z. B. eine Datenbank, die LINQ to SQL verwendet, sind die Kosten für die Ausführung von ToList viel höher, da bei Verwendung von ToList mit LINQ to SQL anstelle der verzögerten Ausführung dh das Laden von Elementen bei Bedarf (was von Vorteil sein kann) In vielen Szenarien werden Elemente aus der Datenbank sofort in den Speicher geladen
quelle
Es wird so (in) effizient sein wie:
Wenn Sie den Quellcode des Konstruktors, der ein benötigt
IEnumerable<T>
, zerlegen , werden Sie sehen, dass er einige Dinge tut:Rufen Sie auf
collection.Count
. Wenn dies der Fallcollection
istIEnumerable<T>
, wird die Ausführung erzwungen. Wenncollection
es sich um ein Array, eine Liste usw. handelt, sollte dies der Fall seinO(1)
.Wenn
collection
implementiertICollection<T>
, werden die Elemente mithilfe derICollection<T>.CopyTo
Methode in einem internen Array gespeichert. Es sollte seinO(n)
, wobein
die Länge der Sammlung.Wenn
collection
dies nicht implementiert wirdICollection<T>
, werden die Elemente der Sammlung durchlaufen und einer internen Liste hinzugefügt.Also, ja, es wird mehr Speicher verbrauchen, da es eine neue Liste erstellen muss, und im schlimmsten Fall wird es das sein
O(n)
, da es das durchlaufen wirdcollection
, um eine Kopie jedes Elements zu erstellen .quelle
0(n)
won
ist die Gesamtsumme der Bytes, die die Zeichenfolgen in der ursprünglichen Sammlung belegen, nicht die Anzahl der Elemente (genauerbool
,int
Etc.)? Sie müssen nicht wirklich eine Kopie jeder Zeichenfolge in der Sammlung erstellen. Sie fügen sie einfach der neuen Liste hinzu.In Anbetracht der Leistung beim Abrufen der Dateiliste
ToList()
ist dies vernachlässigbar. Aber nicht wirklich für andere Szenarien. Das hängt wirklich davon ab, wo Sie es verwenden.Wenn Sie ein Array, eine Liste oder eine andere Sammlung aufrufen, erstellen Sie eine Kopie der Sammlung als
List<T>
. Die Leistung hängt hier von der Größe der Liste ab. Sie sollten es tun, wenn es wirklich notwendig ist.In Ihrem Beispiel rufen Sie es in einem Array auf. Es iteriert über das Array und fügt die Elemente einzeln zu einer neu erstellten Liste hinzu. Die Auswirkungen auf die Leistung hängen also von der Anzahl der Dateien ab.
Wenn Sie ein aufrufen
IEnumerable<T>
, materialisieren Sie dasIEnumerable<T>
(normalerweise eine Abfrage).quelle
ToList Erstellt eine neue Liste und kopiert Elemente aus der Originalquelle in die neu erstellte Liste. Sie müssen also nur die Elemente aus der Originalquelle kopieren und hängen von der Quellgröße ab
quelle