it-swarm.com.de

HashSet vs. List-Leistung

Es ist klar, dass die Suchleistung der generischen Klasse HashSet<T> Höher ist als die der generischen Klasse List<T>. Vergleichen Sie einfach den Hash-basierten Schlüssel mit dem linearen Ansatz in der Klasse List<T>.

Die Berechnung eines Hash-Schlüssels kann jedoch einige CPU-Zyklen in Anspruch nehmen, sodass die lineare Suche für eine kleine Anzahl von Elementen eine echte Alternative zu HashSet<T> Sein kann.

Meine Frage: Wo ist die Gewinnschwelle?

Um das Szenario zu vereinfachen (und fair zu sein), nehmen wir an, dass die Klasse List<T> Die Methode Equals() des Elements verwendet, um ein Element zu identifizieren.

362
Michael Damatov

Viele Leute sagen, dass HashSet<T> Immer besser ist als List<T>, Wenn Sie erst einmal die Größe erreicht haben, bei der Geschwindigkeit eine Rolle spielt, aber das hängt davon ab, was Sie tun.

Angenommen, Sie haben einen List<T>, Der durchschnittlich nur 5 Elemente enthält. Wenn in einer großen Anzahl von Zyklen ein einzelnes Element in jedem Zyklus hinzugefügt oder entfernt wird, ist es möglicherweise besser, einen List<T> Zu verwenden.

Ich habe dies auf meinem Computer getestet und es muss sehr klein sein, um den Vorteil von List<T> Zu erzielen. Bei einer Liste mit kurzen Zeichenfolgen verschwand der Vorteil nach Größe 5, bei Objekten nach Größe 20.

1 item LIST strs time: 617ms
1 item HASHSET strs time: 1332ms

2 item LIST strs time: 781ms
2 item HASHSET strs time: 1354ms

3 item LIST strs time: 950ms
3 item HASHSET strs time: 1405ms

4 item LIST strs time: 1126ms
4 item HASHSET strs time: 1441ms

5 item LIST strs time: 1370ms
5 item HASHSET strs time: 1452ms

6 item LIST strs time: 1481ms
6 item HASHSET strs time: 1418ms

7 item LIST strs time: 1581ms
7 item HASHSET strs time: 1464ms

8 item LIST strs time: 1726ms
8 item HASHSET strs time: 1398ms

9 item LIST strs time: 1901ms
9 item HASHSET strs time: 1433ms

1 item LIST objs time: 614ms
1 item HASHSET objs time: 1993ms

4 item LIST objs time: 837ms
4 item HASHSET objs time: 1914ms

7 item LIST objs time: 1070ms
7 item HASHSET objs time: 1900ms

10 item LIST objs time: 1267ms
10 item HASHSET objs time: 1904ms

13 item LIST objs time: 1494ms
13 item HASHSET objs time: 1893ms

16 item LIST objs time: 1695ms
16 item HASHSET objs time: 1879ms

19 item LIST objs time: 1902ms
19 item HASHSET objs time: 1950ms

22 item LIST objs time: 2136ms
22 item HASHSET objs time: 1893ms

25 item LIST objs time: 2357ms
25 item HASHSET objs time: 1826ms

28 item LIST objs time: 2555ms
28 item HASHSET objs time: 1865ms

31 item LIST objs time: 2755ms
31 item HASHSET objs time: 1963ms

34 item LIST objs time: 3025ms
34 item HASHSET objs time: 1874ms

37 item LIST objs time: 3195ms
37 item HASHSET objs time: 1958ms

40 item LIST objs time: 3401ms
40 item HASHSET objs time: 1855ms

43 item LIST objs time: 3618ms
43 item HASHSET objs time: 1869ms

46 item LIST objs time: 3883ms
46 item HASHSET objs time: 2046ms

49 item LIST objs time: 4218ms
49 item HASHSET objs time: 1873ms

Hier sind die Daten als Grafik dargestellt:

enter image description here

Hier ist der Code:

static void Main(string[] args)
{
    int times = 10000000;


    for (int listSize = 1; listSize < 10; listSize++)
    {
        List<string> list = new List<string>();
        HashSet<string> hashset = new HashSet<string>();

        for (int i = 0; i < listSize; i++)
        {
            list.Add("string" + i.ToString());
            hashset.Add("string" + i.ToString());
        }

        Stopwatch timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            list.Remove("string0");
            list.Add("string0");
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item LIST strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");


        timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            hashset.Remove("string0");
            hashset.Add("string0");
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item HASHSET strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
        Console.WriteLine();
    }


    for (int listSize = 1; listSize < 50; listSize+=3)
    {
        List<object> list = new List<object>();
        HashSet<object> hashset = new HashSet<object>();

        for (int i = 0; i < listSize; i++)
        {
            list.Add(new object());
            hashset.Add(new object());
        }

        object objToAddRem = list[0];

        Stopwatch timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            list.Remove(objToAddRem);
            list.Add(objToAddRem);
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item LIST objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");



        timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            hashset.Remove(objToAddRem);
            hashset.Add(objToAddRem);
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item HASHSET objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
        Console.WriteLine();
    }

    Console.ReadLine();
}
754
innominate227

Du siehst das falsch an. Ja, eine lineare Suche in einer Liste schlägt ein HashSet für eine kleine Anzahl von Elementen. Bei so kleinen Kollektionen spielt der Leistungsunterschied jedoch normalerweise keine Rolle. Es sind im Allgemeinen die großen Sammlungen, um die Sie sich Sorgen machen müssen, und hier können Sie in Big-O ausgedrückt . Wenn Sie jedoch einen echten Engpass bei der HashSet-Leistung gemessen haben, können Sie versuchen, eine hybride Liste/ein hybrides HashSet zu erstellen. Dazu führen Sie jedoch zahlreiche empirische Leistungstests durch und stellen keine Fragen zu SO.

68
Eloff

Es kommt darauf an, ob ein HashSet <> oder eine Liste <> verwendet wird wie Sie auf Ihre Sammlung zugreifen müssen. Wenn Sie die Reihenfolge der Artikel garantieren müssen, verwenden Sie eine Liste. Wenn Sie dies nicht tun, verwenden Sie ein HashSet. Lassen Sie Microsoft sich Gedanken über die Implementierung ihrer Hashing-Algorithmen und -Objekte machen.

Ein HashSet greift auf Elemente zu, ohne die Auflistung aufzählen zu müssen (Komplexität von O (1) oder in der Nähe davon), und da eine Liste im Gegensatz zu einem HashSet die Reihenfolge garantiert, müssen einige Elemente aufgezählt werden (Komplexität) von O (n)).

49
core

Ich dachte nur, ich würde einige Benchmarks für verschiedene Szenarien verwenden, um die vorherigen Antworten zu veranschaulichen:

  1. Einige (12 - 20) kleine Zeichenfolgen (Länge zwischen 5 und 10 Zeichen)
  2. Viele (~ 10K) kleine Zeichenfolgen
  3. Einige lange Zeichenfolgen (Länge zwischen 200 und 1000 Zeichen)
  4. Viele (~ 5K) lange Saiten
  5. Ein paar ganze Zahlen
  6. Viele (~ 10K) ganze Zahlen

Und für jedes Szenario suchen Sie nach Werten, die angezeigt werden:

  1. Am Anfang der Liste ("Start", Index 0)
  2. Nahe am Anfang der Liste ("früh", Index 1)
  3. In der Mitte der Liste ("Mitte", Indexzahl/2)
  4. Nahe dem Ende der Liste ("spät", Indexzähler-2)
  5. Am Ende der Liste ("Ende", Indexzähler-1)

Vor jedem Szenario habe ich Listen mit zufälligen Zeichenfolgen in zufälliger Größe generiert und dann jede Liste einem Hashset zugeführt. Jedes Szenario wurde 10.000 Mal ausgeführt. Im Wesentlichen:

(Testpseudocode)

stopwatch.start
for X times
    exists = list.Contains(lookup);
stopwatch.stop

stopwatch.start
for X times
    exists = hashset.Contains(lookup);
stopwatch.stop

Beispielausgabe

Getestet unter Windows 7, 12 GB RAM, 64 Bit, Xeon 2,8 GHz

---------- Testing few small strings ------------
Sample items: (16 total)
vgnwaloqf diwfpxbv tdcdc grfch icsjwk
...

Benchmarks:
1: hashset: late -- 100.00 % -- [Elapsed: 0.0018398 sec]
2: hashset: middle -- 104.19 % -- [Elapsed: 0.0019169 sec]
3: hashset: end -- 108.21 % -- [Elapsed: 0.0019908 sec]
4: list: early -- 144.62 % -- [Elapsed: 0.0026607 sec]
5: hashset: start -- 174.32 % -- [Elapsed: 0.0032071 sec]
6: list: middle -- 187.72 % -- [Elapsed: 0.0034536 sec]
7: list: late -- 192.66 % -- [Elapsed: 0.0035446 sec]
8: list: end -- 215.42 % -- [Elapsed: 0.0039633 sec]
9: hashset: early -- 217.95 % -- [Elapsed: 0.0040098 sec]
10: list: start -- 576.55 % -- [Elapsed: 0.0106073 sec]


---------- Testing many small strings ------------
Sample items: (10346 total)
dmnowa yshtrxorj vthjk okrxegip vwpoltck
...

Benchmarks:
1: hashset: end -- 100.00 % -- [Elapsed: 0.0017443 sec]
2: hashset: late -- 102.91 % -- [Elapsed: 0.0017951 sec]
3: hashset: middle -- 106.23 % -- [Elapsed: 0.0018529 sec]
4: list: early -- 107.49 % -- [Elapsed: 0.0018749 sec]
5: list: start -- 126.23 % -- [Elapsed: 0.0022018 sec]
6: hashset: early -- 134.11 % -- [Elapsed: 0.0023393 sec]
7: hashset: start -- 372.09 % -- [Elapsed: 0.0064903 sec]
8: list: middle -- 48,593.79 % -- [Elapsed: 0.8476214 sec]
9: list: end -- 99,020.73 % -- [Elapsed: 1.7272186 sec]
10: list: late -- 99,089.36 % -- [Elapsed: 1.7284155 sec]


---------- Testing few long strings ------------
Sample items: (19 total)
hidfymjyjtffcjmlcaoivbylakmqgoiowbgxpyhnrreodxyleehkhsofjqenyrrtlphbcnvdrbqdvji...
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0018266 sec]
2: list: start -- 115.76 % -- [Elapsed: 0.0021144 sec]
3: list: middle -- 143.44 % -- [Elapsed: 0.0026201 sec]
4: list: late -- 190.05 % -- [Elapsed: 0.0034715 sec]
5: list: end -- 193.78 % -- [Elapsed: 0.0035395 sec]
6: hashset: early -- 215.00 % -- [Elapsed: 0.0039271 sec]
7: hashset: end -- 248.47 % -- [Elapsed: 0.0045386 sec]
8: hashset: start -- 298.04 % -- [Elapsed: 0.005444 sec]
9: hashset: middle -- 325.63 % -- [Elapsed: 0.005948 sec]
10: hashset: late -- 431.62 % -- [Elapsed: 0.0078839 sec]


---------- Testing many long strings ------------
Sample items: (5000 total)
yrpjccgxjbketcpmnvyqvghhlnjblhgimybdygumtijtrwaromwrajlsjhxoselbucqualmhbmwnvnpnm
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0016211 sec]
2: list: start -- 132.73 % -- [Elapsed: 0.0021517 sec]
3: hashset: start -- 231.26 % -- [Elapsed: 0.003749 sec]
4: hashset: end -- 368.74 % -- [Elapsed: 0.0059776 sec]
5: hashset: middle -- 385.50 % -- [Elapsed: 0.0062493 sec]
6: hashset: late -- 406.23 % -- [Elapsed: 0.0065854 sec]
7: hashset: early -- 421.34 % -- [Elapsed: 0.0068304 sec]
8: list: middle -- 18,619.12 % -- [Elapsed: 0.3018345 sec]
9: list: end -- 40,942.82 % -- [Elapsed: 0.663724 sec]
10: list: late -- 41,188.19 % -- [Elapsed: 0.6677017 sec]


---------- Testing few ints ------------
Sample items: (16 total)
7266092 60668895 159021363 216428460 28007724
...

Benchmarks:
1: hashset: early -- 100.00 % -- [Elapsed: 0.0016211 sec]
2: hashset: end -- 100.45 % -- [Elapsed: 0.0016284 sec]
3: list: early -- 101.83 % -- [Elapsed: 0.0016507 sec]
4: hashset: late -- 108.95 % -- [Elapsed: 0.0017662 sec]
5: hashset: middle -- 112.29 % -- [Elapsed: 0.0018204 sec]
6: hashset: start -- 120.33 % -- [Elapsed: 0.0019506 sec]
7: list: late -- 134.45 % -- [Elapsed: 0.0021795 sec]
8: list: start -- 136.43 % -- [Elapsed: 0.0022117 sec]
9: list: end -- 169.77 % -- [Elapsed: 0.0027522 sec]
10: list: middle -- 237.94 % -- [Elapsed: 0.0038573 sec]


---------- Testing many ints ------------
Sample items: (10357 total)
370826556 569127161 101235820 792075135 270823009
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0015132 sec]
2: hashset: end -- 101.79 % -- [Elapsed: 0.0015403 sec]
3: hashset: early -- 102.08 % -- [Elapsed: 0.0015446 sec]
4: hashset: middle -- 103.21 % -- [Elapsed: 0.0015618 sec]
5: hashset: late -- 104.26 % -- [Elapsed: 0.0015776 sec]
6: list: start -- 126.78 % -- [Elapsed: 0.0019184 sec]
7: hashset: start -- 130.91 % -- [Elapsed: 0.0019809 sec]
8: list: middle -- 16,497.89 % -- [Elapsed: 0.2496461 sec]
9: list: end -- 32,715.52 % -- [Elapsed: 0.4950512 sec]
10: list: late -- 33,698.87 % -- [Elapsed: 0.5099313 sec]
23
drzaus

Die Gewinnschwelle hängt von den Kosten für die Berechnung des Hash ab. Hash-Berechnungen können trivial sein oder auch nicht ... :-) Es gibt immer die System.Collections.Specialized.HybridDictionary-Klasse, damit Sie sich keine Sorgen um die Gewinnschwelle machen müssen.

10
Walden Leverich

Die Antwort lautet wie immer "es kommt darauf an". Ich gehe von den Tags aus, von denen Sie sprechen, von C # aus.

Ihre beste Wette ist zu bestimmen

  1. Ein Datensatz
  2. Verwendungsanforderungen

und schreiben Sie einige Testfälle.

Es hängt auch davon ab, wie Sie die Liste sortieren (falls überhaupt), welche Art von Vergleichen durchgeführt werden müssen, wie lange der "Vergleichen" -Vorgang für das bestimmte Objekt in der Liste dauert oder sogar wie Sie die Liste verwenden möchten Sammlung.

Im Allgemeinen richtet sich die beste Auswahl weniger nach der Größe der Daten, mit denen Sie arbeiten, als vielmehr danach, wie Sie darauf zugreifen möchten. Haben Sie jedes Datenelement mit einer bestimmten Zeichenfolge oder mit anderen Daten verknüpft? Eine Hash-basierte Sammlung wäre wahrscheinlich am besten. Ist die Reihenfolge der von Ihnen gespeicherten Daten wichtig oder müssen Sie gleichzeitig auf alle Daten zugreifen? Eine reguläre Liste kann dann besser sein.

Zusätzlich:

Natürlich gehen meine obigen Kommentare davon aus, dass "Leistung" Datenzugriff bedeutet. Noch etwas zu beachten: Wonach suchen Sie, wenn Sie "Leistung" sagen? Ist Leistung individueller Wert nachschlagen? Ist es die Verwaltung von großen (10000, 100000 oder mehr) Wertesätzen? Ist es die Leistung, die Datenstruktur mit Daten zu füllen? Daten entfernen? Zugriff auf einzelne Datenbits? Werte ersetzen? Über die Werte iterieren? Speichernutzung? Datenkopiergeschwindigkeit? Wenn Sie beispielsweise über einen Zeichenfolgenwert auf Daten zugreifen, Ihre Hauptleistungsanforderung jedoch die minimale Speichernutzung ist, kann es zu Konflikten beim Entwurf kommen.

6
Robert P

Sie können ein HybridDictionary verwenden, das den Bruchpunkt automatisch erkennt und Nullwerte akzeptiert, sodass er im Wesentlichen mit einem HashSet identisch ist.

5
Muis

Es hängt davon ab, ob. Wenn die genaue Antwort wirklich wichtig ist, machen Sie eine Profilerstellung und finden Sie es heraus. Wenn Sie sicher sind, dass Sie nie mehr als eine bestimmte Anzahl von Elementen im Set haben, wählen Sie eine Liste. Wenn die Anzahl nicht begrenzt ist, verwenden Sie ein HashSet.

4
Adam Rosenfield

Ein Faktor, den Sie nicht berücksichtigen, ist die Robustheit der Funktion GetHashcode (). Mit einer perfekten Hash-Funktion hat das HashSet eindeutig eine bessere Suchleistung. Mit abnehmender Hash-Funktion verringert sich auch die HashSet-Suchzeit.

3
JaredPar

Kommt darauf an, was du haschst. Wenn es sich bei Ihren Schlüsseln um Ganzzahlen handelt, benötigen Sie wahrscheinlich nicht viele Elemente, bevor das HashSet schneller ist. Wenn Sie es auf einer Zeichenfolge eingeben, ist es langsamer und hängt von der eingegebenen Zeichenfolge ab.

Sicherlich könnten Sie ziemlich leicht einen Benchmark aufstellen?

3
Peter

Hängt von vielen Faktoren ab ... Listenimplementierung, CPU-Architektur, JVM, Schleifensemantik, Komplexität der Gleichheitsmethode usw. ... Bis die Liste groß genug wird, um ein effektives Benchmarking (über 1000 Elemente) von Hash-basierten Binärdateien durchzuführen Suchvorgänge schlagen lineare Suchvorgänge zweifellos und der Unterschied steigt nur von dort an.

Hoffe das hilft!

0
Kyle