Gibt es Auswirkungen auf die Leistung beim Aufrufen von ToList ()?

139

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?

Cody
quelle
+1 interessiert, die Antwort auch hier zu erfahren. IMHO , es sei denn die App - Leistung entscheidend ist, glaube ich , immer verwenden würde 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).
September
Das Erstellen einer Liste aus einem Array sollte sehr billig sein.
Leppie
2
@Sepster Ich gebe den Datentyp nur so genau an, wie ich einen Job machen muss. Wenn ich nicht anrufen muss Addoder Remove, würde ich es als IEnumerable<T>(oder noch besser var)
belassen
4
Ich denke, in diesem Fall ist es besser, nennen EnumerateFilesstatt GetFiles, so dass nur ein Array erstellt wird.
Tukaef
3
GetFiles(directory), wie es derzeit in .NET implementiert ist, tut es so ziemlich new List<string>(EnumerateFiles(directory)).ToArray(). So GetFiles(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 .
Joren

Antworten:

178

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 den List(IEnumerable<T> collection)Konstruktor. Dieser Konstruktor muss eine Kopie des Arrays erstellen (allgemeiner IEnumerable<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, Asvs.To

Sie werden feststellen, dass es in LINQ verschiedene Methoden gibt, die mit As(wie AsEnumerable()) und To(wie ToList()) beginnen. Die Methoden, die mit beginnen, Toerfordern eine Konvertierung wie oben (dh sie können sich auf die Leistung auswirken), und die Methoden, die mit beginnen, erfordern und erfordern nur eine Umwandlung Asoder 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 Capacityund CountAttribute auf List<T>. Capacitybezieht sich auf die Größe des Arrays hinter den Kulissen, Countist die Anzahl der Elemente in der List<T>immer ist <= Capacity. Wenn also ein Element zur Liste hinzugefügt wird Capacity, wird die Größe des Elements List<T>verdoppelt und das Array kopiert.

Daniel Imms
quelle
2
Ich wollte nur betonen, dass der 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 nicht ICollection<T>der Fall ist, durchläuft der Konstruktor diese und ruft Addjedes Element auf.
Justinas Simanavicius
Es ist wichtig zu beachten, dass Sie ToList () häufig als irreführend anspruchsvolle Operation ansehen. Dies geschieht, wenn Sie über eine LINQ-Abfrage eine IEnumerable <> erstellen. Die Linq-Abfrage wird erstellt, aber nicht ausgeführt. Das Aufrufen von ToList () führt die Abfrage aus und scheint daher ressourcenintensiv zu sein - aber es ist die Abfrage, die intensiv ist und nicht die Operation ToList () (es sei denn, es handelt sich um eine wirklich große Liste)
dancer42
36

Gibt es Auswirkungen auf die Leistung beim Aufruf von toList ()?

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 .ToListdas

Wenn Sie aufrufen .ToList, ruft der Code auf , Enumerable.ToList()was eine Erweiterungsmethode ist, die return 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.GetFilesWenn 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.EnumerateFilesin C # 4. Dies ähnelt eher einer Abfrage, die nicht sofort ausgeführt wird. Sie können weitere Abfragen hinzufügen, z. B.:

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

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.

Cheng Chen
quelle
19

Gibt es Auswirkungen auf die Leistung beim Aufruf von toList ()?

Ja da ist. Mit der Erweiterungsmethode Enumerable.ToList()wird ein neues List<T>Objekt aus der IEnumerable<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. Daher List<T>wird ein übergroßes Array zum Speichern der Elemente der Liste verwendet. Wenn das List<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:

  1. Die Quellensammlung implementiert ICollection<T>: Anschließend ICollection<T>.Countwird die genaue Größe der Quellensammlung ermittelt und ein passendes Sicherungsarray zugewiesen, bevor alle Elemente der Quellensammlung mit in das Sicherungsarray kopiert werden ICollection<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.

  2. Andernfalls ist die Größe der Quellensammlung unbekannt und der Enumerator von IEnumerable<T>wird verwendet, um jedes Quellelement einzeln zum neuen hinzuzufügen List<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 Aufrufen ToList()wird einfach das Quellarray kopiert und in ein List<T>Objekt eingeschlossen. Auch die Leistung des zweiten Falles ist für kleine Sammlungen kein Grund zur Sorge.

Martin Liversage
quelle
5

"Gibt es eine Auswirkung auf die Leistung, die berücksichtigt werden muss?"

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! :) :)

jross
quelle
4

ToList()erstellt eine neue Liste und fügt die Elemente darin ein, was bedeutet, dass damit verbundene Kosten verbunden sind ToList(). 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

Haris Hasan
quelle
Haris: Was ich über die Originalquelle nicht sicher bin, was mit der Originalquelle passieren wird, nachdem ich die ToList ()
aufgerufen habe
@ Saurabh GC wird es aufräumen
pswg
@ Saurabh nichts wird mit der Originalquelle passieren. Elemente der Originalquelle werden von der neu erstellten Liste referenziert
Haris Hasan
"Wenn Sie nur die Sammlung durchlaufen möchten, müssen Sie keine ToList ausführen" - wie sollten Sie also iterieren?
SharpC
4

Es wird so (in) effizient sein wie:

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

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 Fall collectionist IEnumerable<T>, wird die Ausführung erzwungen. Wenn collectiones sich um ein Array, eine Liste usw. handelt, sollte dies der Fall sein O(1).

  • Wenn collectionimplementiert ICollection<T>, werden die Elemente mithilfe der ICollection<T>.CopyToMethode in einem internen Array gespeichert. Es sollte sein O(n), wobei ndie Länge der Sammlung.

  • Wenn collectiondies nicht implementiert wird ICollection<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 seinO(n) , da es das durchlaufen wird collection, um eine Kopie jedes Elements zu erstellen .

Oscar Mederos
quelle
3
Schließen, 0(n)wo nist die Gesamtsumme der Bytes, die die Zeichenfolgen in der ursprünglichen Sammlung belegen, nicht die Anzahl der Elemente (genauer
gesagt
@ user1416420 Ich könnte mich irren, aber warum ist das so? Was ist, wenn es sich um eine Sammlung von einem anderen Typ (zB. bool, intEtc.)? Sie müssen nicht wirklich eine Kopie jeder Zeichenfolge in der Sammlung erstellen. Sie fügen sie einfach der neuen Liste hinzu.
Oscar Mederos
Es spielt immer noch keine Rolle, ob die neue Speicherzuweisung und das Kopieren von Bytes diese Methode beendet. Ein Bool belegt in .NET auch 4 Byte. Tatsächlich ist jede Referenz eines Objekts in .NET mindestens 8 Byte lang, also ziemlich langsam. Die ersten 4 Bytes zeigen auf die
Typentabelle
3

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 das IEnumerable<T>(normalerweise eine Abfrage).

Mohammad Dehghan
quelle
2

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

TalentTuner
quelle