Wörtlich genommen, ist mir keine bestehende Implementierung von Map oder Set bekannt, die niemals erzeugt beim Hinzufügen und Entfernen eines Schlüssels beliebigen Müll.
Tatsächlich ist die einzige Möglichkeit, die technisch möglich wäre (in Java, unter Verwendung der Map
y Set
APIs) ist, wenn Sie eine strenge Obergrenze für die Anzahl der Einträge festlegen würden. Praktische Map- und Set-Implementierungen benötigen einen zusätzlichen Zustand, der proportional zur Anzahl der Elemente ist, die sie enthalten. Dieser Zustand muss irgendwo gespeichert werden, und wenn die aktuelle Zuweisung überschritten wird, muss dieser Speicher erweitert werden. In Java bedeutet dies, dass neue Knoten zugewiesen werden müssen.
(OK, man könnte eine Datenstrukturklasse entwerfen, die alte nutzlose Knoten für immer festhält und daher nie irgendwelche sammelbar Müll ... aber er erzeugt immer noch Müll).
Was können Sie also in der Praxis tun, um ... reduzieren. die Menge des erzeugten Mülls. Nehmen wir HashMap
als Beispiel:
-
Wenn Sie einen Eintrag entfernen, entsteht Müll. Dies ist unvermeidlich, es sei denn, Sie ersetzen die Hash-Ketten durch eine Implementierung, die die Knoten, die die Ketteneinträge darstellen, niemals freigibt. (Und das ist eine schlechte Idee ... es sei denn, Sie können garantieren, dass die Größe des freien Knotenpools immer klein sein wird. Siehe unten für warum Es ist eine schlechte Idee.)
-
Wenn die Größe des Haupt-Hash-Arrays geändert wird, entsteht Müll. Dies kann auf mehrere Arten vermieden werden:
-
Sie können ein 'capacity'-Argument im HashMap-Konstruktor angeben, um die Größe des anfänglichen Hash-Arrays so groß zu machen, dass Sie es nie ändern müssen. (Aber das verschwendet potentiell Platz ... besonders wenn Sie nicht genau vorhersagen können, wie groß das HashMap
wird wachsen.)
-
Sie können einen lächerlichen Wert für das 'load factor'-Argument angeben, um die HashMap zu veranlassen, ihre Größe nie zu ändern. (Aber das führt zu einer HashMap, deren Hash-Ketten nicht begrenzt sind, und Sie enden mit O(N)
Verhalten beim Nachschlagen, Einfügen, Löschen usw.
Tatsächlich ist die Erzeugung von Müll nicht zwangsläufig schlecht für Leistung. Das Festhalten an Knoten, damit der Garbage Collector sie nicht einsammelt, ist in der Tat kann sogar noch schlimmer sein für Leistung.
Die Kosten eines GC-Laufs (unter der Annahme eines modernen Kopiersammlers) verteilen sich hauptsächlich auf drei Bereiche:
- Auffinden von Knoten, die kein Müll sind.
- Kopieren dieser Nicht-Müll-Knoten in den "Nach-Raum".
- Aktualisierung von Verweisen in anderen Nicht-Müll-Knoten, um auf Objekte im "Zu-Raum" zu zeigen.
(Wenn Sie einen Kollektor mit geringem Pausenanteil verwenden, fallen auch andere Kosten an ... im Allgemeinen proportional zur Menge des Nicht-Mülls).
Der einzige Teil der Arbeit des GC, der tatsächlich von der Menge an Garbage abhängt, ist das Löschen des Speichers, den die Garbage-Objekte einst belegten, um ihn für die Wiederverwendung bereit zu machen. Und das kann mit einem einzigen bzero
Aufruf des gesamten "Von-Raums" ... oder Verwendung von Tricks des virtuellen Speichers.
Angenommen, Ihre Anwendung/Datenstruktur bleibt an Knoten hängen, um die Erzeugung von Müll zu vermeiden. Wenn nun die GC läuft, muss sie zusätzliche Arbeit leisten, um all diese zusätzlichen Knoten zu durchlaufen und sie in den "to-space" zu kopieren, obwohl sie keine nützlichen Informationen enthalten. Außerdem verbrauchen diese Knoten Speicher, was bedeutet, dass weniger Platz zur Verfügung steht, wenn der Rest der Anwendung Garbage erzeugt, und die GC häufiger ausgeführt werden muss.
Und wenn Sie schwache/weiche Referenzen verwendet haben, damit die GC Knoten aus Ihrer Datenstruktur zurückholen kann, dann bedeutet das noch mehr Arbeit für die GC ... und Platz, um diese Referenzen darzustellen.
Hinweis: Ich behaupte nicht, dass das Pooling von Objekten die Leistung immer verschlechtert, sondern nur, dass dies häufig der Fall ist, insbesondere wenn der Pool unerwartet groß wird.
Und natürlich ist das der Grund, warum HashMap und ähnliche allgemeine Datenstrukturklassen kein Objekt-Pooling betreiben. Wenn sie es täten, würden sie in Situationen, in denen der Programmierer es nicht erwartet, deutlich schlechter abschneiden ... und sie wirklich gebrochen werden würde , IMO.
Schließlich gibt es eine einfache Möglichkeit, eine HashMap so abzustimmen, dass ein Hinzufügen unmittelbar gefolgt von einem Entfernen desselben Schlüssels (garantiert) keinen Müll erzeugt. Verpacken Sie sie in eine Map-Klasse, die den letzten "hinzugefügten" Eintrag zwischenspeichert und nur die put
in der Realität HashMap
wenn der nächste Eintrag hinzugefügt wird. Natürlich ist dies KEINE Allzwecklösung, aber es entspricht dem Anwendungsfall Ihrer früheren Frage.
3 Stimmen
Das Entry-Objekt wird erst freigegeben, wenn Sie den Eintrag wieder entfernen, es ist also kaum "Müll" (es ist ein wesentlicher Bestandteil der Hashtabelle). Und selbst wenn es das wäre, warum ist das ein Problem? Was kommt als nächstes? Abschaffung der primitiven Wrapper?
0 Stimmen
Das habe ich auch gesagt. Wenn Sie ein Objekt hinzufügen und entfernen, erzeugen Sie Müll für die GC. Dies ist nur ein Problem, wenn Sie mit Echtzeitsystemen arbeiten, die sich keine GC-Latenz leisten können. Javolution wurde für Echtzeitsysteme entwickelt, aber es funktioniert nicht (toll!). Ich stimme mit Ihnen überein. In 99% der Fälle ist dies kein Problem. Aber für ein Echtzeitsystem IST es leider ein Problem.
0 Stimmen
Welche Art von Schlüsseln und Werten haben Sie? Muss es mit einem beliebigen Objekt funktionieren?
0 Stimmen
Map<Object, Object>. Keine Primitive :( Es sieht so aus, als ob Trove nur Primitive unterstützt, was in einigen Fällen sehr nützlich ist, aber nicht in diesem :(
0 Stimmen
Es scheint, dass Ihr Problem mit der anderen Bibliothek die Größenanpassung war, wenn die Karte wächst. Das müssen wahrscheinlich alle Bibliotheken tun. Ist es nicht möglich, die Größe im Voraus auf eine maximale Kapazität einzustellen?
0 Stimmen
Warum wollen Sie Java in einer Umgebung verwenden, in der die Ausführung von GC ein Problem darstellt?
0 Stimmen
@Thilo Die Größe ist NIE größer als eins. Es handelt sich also nicht um ein Übergrößenproblem. Lesen Sie meine Antwort auf Ihre andere Frage.
9 Stimmen
Haben Sie einen guten Grund für diesen Bedarf? Warum glauben Sie, dass z. B. eine HashMap nicht für Echtzeitsysteme geeignet ist? Haben Sie versucht, sie zu verwenden? Haben Sie die Leistung wirklich gemessen und für inakzeptabel befunden? Wenn es Ihnen wirklich so ernst ist mit dem Zeit- und Speicherverbrauch, warum schreiben Sie es dann nicht in einfachem C? Dann wird es blitzschnell sein und keine Ressourcen verbrauchen, die eine JVM verbraucht.
0 Stimmen
Okay, das war also ein Fehler. Wird Ihre echte Karte auch nur einen (oder sehr wenige) Einträge haben? Wenn ja, dann gibt es dafür spezielle Implementierungen.
0 Stimmen
@Attila Das verdient ein eigenes Thema. Sie können zwischen C++, RTSJ und Java wählen. Wir entscheiden uns für Java, sorry.
1 Stimmen
Das scheint eine sehr schlechte Wahl zu sein, wenn selbst die Leistung sehr einfacher Datentypen für Ihre Bedürfnisse nicht ausreichend ist.