Kann mir jemand erklären, warum die Generika- List.Contains()
Funktion so langsam ist?
Ich habe eine List<long>
mit ungefähr einer Million Nummern und den Code, der ständig überprüft, ob diese Nummern eine bestimmte Nummer enthalten.
Ich habe versucht, dasselbe mit Dictionary<long, byte>
und der Dictionary.ContainsKey()
Funktion zu tun , und es war ungefähr 10-20 Mal schneller als mit der Liste.
Natürlich möchte ich Dictionary nicht wirklich für diesen Zweck verwenden, da es nicht so verwendet werden sollte.
Die eigentliche Frage hier ist also, gibt es eine Alternative zu der List<T>.Contains()
, aber nicht so verrückten wie Dictionary<K,V>.ContainsKey()
?
Antworten:
Wenn Sie nur nach Existenz suchen, ist
HashSet<T>
in .NET 3.5 die beste Option - wörterbuchähnliche Leistung, aber kein Schlüssel / Wert-Paar - nur die Werte:quelle
List.Contains ist eine O (n) -Operation.
Dictionary.ContainsKey ist eine O (1) -Operation, da der Hashcode der Objekte als Schlüssel verwendet wird, wodurch Sie schneller suchen können.
Ich denke nicht, dass es eine gute Idee ist, eine Liste zu haben, die eine Million Einträge enthält. Ich glaube nicht, dass die List-Klasse für diesen Zweck entwickelt wurde. :) :)
Ist es nicht möglich, diese Millon-Entitäten beispielsweise in einem RDBMS zu speichern und Abfragen in dieser Datenbank durchzuführen?
Wenn es nicht möglich ist, würde ich trotzdem ein Wörterbuch verwenden.
quelle
Ich glaube ich habe die Antwort! Ja, es stimmt, dass Contains () in einer Liste (Array) O (n) ist. Wenn das Array jedoch kurz ist und Sie Werttypen verwenden, sollte es dennoch recht schnell sein. Mit dem CLR-Profiler [kostenloser Download von Microsoft] stellte ich jedoch fest, dass Contains () Boxwerte enthält, um sie zu vergleichen. Dies erfordert eine Heap-Zuweisung, die SEHR teuer (langsam) ist. [Hinweis: Dies ist .Net 2.0; andere .Net-Versionen nicht getestet.]
Hier ist die ganze Geschichte und Lösung. Wir haben eine Aufzählung namens "VI" und eine Klasse namens "ValueIdList" erstellt, die ein abstrakter Typ für eine Liste (ein Array) von VI-Objekten ist. Die ursprüngliche Implementierung war in den alten .Net 1.1-Tagen und es wurde eine gekapselte ArrayList verwendet. Wir haben kürzlich in http://blogs.msdn.com/b/joshwil/archive/2004/04/13/112598.aspx festgestellt, dass eine generische Liste (List <VI>) bei Werttypen (wie unserer) eine viel bessere Leistung als ArrayList aufweist Aufzählung VI), da die Werte nicht eingerahmt werden müssen. Es ist wahr und es hat funktioniert ... fast.
Der CLR-Profiler zeigte eine Überraschung. Hier ist ein Teil des Zuordnungsdiagramms:
Wie Sie sehen können, ruft Contains () überraschenderweise Generic.ObjectEqualityComparer.Equals () auf, was anscheinend das Boxen eines VI-Werts erfordert, was eine teure Heap-Zuweisung erfordert. Es ist seltsam, dass Microsoft das Boxen auf der Liste eliminieren würde, nur um es für eine einfache Operation wie diese erneut zu benötigen.
Unsere Lösung bestand darin, die Implementierung von Contains () neu zu schreiben, was in unserem Fall einfach war, da wir das generische Listenobjekt (_items) bereits gekapselt hatten. Hier ist der einfache Code:
Der Vergleich der VI-Werte wird jetzt in unserer eigenen Version von IndexOf () durchgeführt, die kein Boxen erfordert und sehr schnell ist. Unser spezielles Programm hat sich nach diesem einfachen Umschreiben um 20% beschleunigt. O (n) ... kein Problem! Vermeiden Sie einfach die verschwendete Speichernutzung!
quelle
Contains
Implementierung ist für meinen Anwendungsfall viel schneller.Das Wörterbuch ist nicht so schlecht, weil die Schlüssel in einem Wörterbuch so konzipiert sind, dass sie schnell gefunden werden können. Um eine Nummer in einer Liste zu finden, muss die gesamte Liste durchlaufen werden.
Natürlich funktioniert das Wörterbuch nur, wenn Ihre Nummern eindeutig und nicht geordnet sind.
Ich denke, es gibt auch eine
HashSet<T>
Klasse in .NET 3.5, die auch nur eindeutige Elemente zulässt.quelle
Eine SortedList ist schneller zu suchen (aber langsamer zum Einfügen von Elementen)
quelle
Dies ist nicht gerade eine Antwort auf Ihre Frage, aber ich habe eine Klasse, die die Leistung von Contains () für eine Sammlung erhöht. Ich habe eine Warteschlange untergeordnet und ein Wörterbuch hinzugefügt, das Hashcodes Listen von Objekten zuordnet. Die
Dictionary.Contains()
Funktion O (1) , währendList.Contains()
,Queue.Contains()
undStack.Contains()
ist O (n).Der Werttyp des Wörterbuchs ist eine Warteschlange, die Objekte mit demselben Hashcode enthält. Der Aufrufer kann ein benutzerdefiniertes Klassenobjekt bereitstellen, das IEqualityComparer implementiert. Sie können dieses Muster für Stapel oder Listen verwenden. Der Code würde nur ein paar Änderungen benötigen.
Meine einfachen Tests zeigen, dass meine
HashQueue.Contains()
viel schneller läuft alsQueue.Contains()
. Das Ausführen des Testcodes mit einer Anzahl von 10.000 dauert 0,00045 Sekunden für die HashQueue-Version und 0,37 Sekunden für die Queue-Version. Bei einer Anzahl von 100.000 dauert die HashQueue-Version 0,0031 Sekunden, während die Warteschlange 36,38 Sekunden benötigt!Hier ist mein Testcode:
quelle
HashQueue, 00:00:00.0004029
Queue, 00:00:00.3901439
HashSet, 00:00:00.0001716
Warum ist ein Wörterbuch unangemessen?
Um festzustellen, ob ein bestimmter Wert in der Liste enthalten ist, müssen Sie die gesamte Liste durchlaufen. Mit einem Wörterbuch (oder einem anderen Hash-basierten Container) können Sie die Anzahl der Objekte, mit denen Sie vergleichen müssen, viel schneller eingrenzen. Der Schlüssel (in Ihrem Fall die Zahl) ist gehasht und gibt dem Wörterbuch die Teilmenge der Objekte, mit denen verglichen werden soll.
quelle
Ich verwende dies im Compact Framework, wo HashSet nicht unterstützt wird. Ich habe mich für ein Wörterbuch entschieden, bei dem beide Zeichenfolgen der gesuchte Wert sind.
Dies bedeutet, dass ich Listenfunktionalität mit Wörterbuchleistung erhalte. Es ist ein bisschen hacky, aber es funktioniert.
quelle
string
Referenz und einbool
Wert einen Unterschied von 3 oder 7 Bytes für 32- bzw. 64-Bit-Systeme. Beachten Sie jedoch, dass die Größe jedes Eintrags auf ein Vielfaches von 4 bzw. 8 aufgerundet wird. Die Wahl zwischenstring
und machtbool
somit möglicherweise überhaupt keinen Unterschied in der Größe. Die leere Zeichenfolge""
ist im Speicher immer bereits als statische Eigenschaft vorhandenstring.Empty
, sodass es keinen Unterschied macht, ob Sie sie im Wörterbuch verwenden oder nicht. (Und es wird sowieso woanders verwendet.)