Duplikate und Klone
« | 04 Oct 2020 | »Im GATE Projekt gibt es, na sagen wir mal, eine Spitzfindigkeit bei Strings. Diese kann man kopieren, duplizieren und klonen.
Diese drei Formen der Vervielfältigung ist in anderen Frameworks nicht üblich. Aber ich habe natürlich einen Grund für diese Unterscheidung…
Aktuell arbeite ich wieder mehr an den Parsern für Datenformate wie XML, JSON und YAML. Für diese sind effiziente String-Routinen entscheidend und aus diesem Grund werden 3 Arten der Vervielfältigung von Stringinstanzen angeboten:
- Kopien:
gate_string_copy(target, source)
erstellt eine vollständige Kopie eines Strings, der keine Verknüpfung mit dem Original hat. Die Kopie ist inhaltsgleich, teilt jedoch weder Referenzzählung noch Speicher mit der ursprünglichen Quelle. - Duplikat:
gate_string_duplicate(target, source)
dupliziert die Referenzen der Quelle auf die neue String-Instanz. Bei einem verwalteten String wird der Referenzzähler erhöht und der Text-Pointer auf die gleiche Adresse gesetzt wie im Quellobjekt.
Bei unverwalteten Strings werden nur die Pointer kopiert und damit darf die neue Instanz nicht länger benutzt werden als das Original bzw. darf es nur so lange benutzt werden, wie die (extern verwalteten) Stringdaten im Speicher zur Verfügung stehen. - Klone:
gate_string_clone(target, source)
ist quasi eine Mischung der beiden anderen, die garantieren soll, dass der Klon nicht von der Lebensdauer des Original beeinflusst werden darf.
Bei verwalteten String-Puffern wird der Referenzzähler erhöht und damit lebt der Puffer so lange, bis die letzte Referenz erlischt.
Ist die Quelle aber unverwaltet, startet ein Kopiervorgang. Es wird eine verwaltete Kopie des unverwalteten Originals erzeugt.
Performance vs. Sicherheit
Duplikate sind der schnellste Weg um Strings zu vervielfältigen, weil es bei ihnen nie zur Allokierung von neuem Speicher kommen kann. Hat man aber mal versehentlich einen “statischen” String dupliziert und weiterverarbeitet, dessen Daten auf dem Stack lagen, dann produziert man einen Crash, sobald der Scope des originalen Puffers verlassen wird.
Kopien wiederum sind die sicherste Variante, verbrauchen aber auch den
meisten Speicher. Sie sind damit der MSVC
Implementierung von std::string
gleichgestellt, die ebenfalls immer neue Kopien anlegen.
Der Vorteil von Klonen liegt in ihrer Effizienz, dass kein Overhead bei
verwalteten Strings produziert wird und das nicht-verwaltete Strings zu
verwalteten Kopien umgestaltet werden.
Es wird also nur einmal Speicher für eine Kopie angefordert und nicht mehrfach.
Einsatzorte
Kopien eignen sich hervorragend für die Weitergabe von Daten zwischen Modulen
wie DLLs oder SOs. Denn obwohl verwaltete Strings durch den Referenzzähler so
lange im Speicher erhalten bleibt, wie die Referenz es will, so stammt
die Destruktorfunktion dennoch aus dem Modul, mit dem der String allokiert
wurde.
Wenn die DLL/SO also
bereits entladen ist, kommt es zum Crash weil der Code zur Löschung nicht mehr
im Speicher vorhanden ist.
Bei einer Kopie wird jedoch die Destruktorfunktion des Moduls benutzt, in
welchem die Kopie angestoßen wurde.
Duplikate eignen sich super für synchrone Parsing-Operationen, die die Stringinhalte oder deren Teilmengen nicht über den Parsing-Prozess hinweg erhalten müssen. Während des Parsings können also zahlreiche Duplikate und Sub-Strings produziert werden, die alle auf die originalen Input-Daten verweisen. Man kann daher auch statische String problemlos in den Duplikaten nutzen und hat garantiert keinen zusätzlichen Speicherbedarf.
Und Klone verwendet man für fast alles andere, weil sie sicher und möglichst schnell sind und weil viele Klone maximal eine oder eben gar keine Allokierung anstoßen.
Rückgabewerte von String-Funktionen
Aus Effizienzgründen liefern die GATE Standard-String-Routinen wie
gate_string_substr()
oder gate_string_trim()
nur manipulierte Duplikate
zurück. Eine nicht-verwaltete Quelle führt also zu einem nicht-verwalteten
Resultat.
Und das gleiche gilt auch für den GATE C++ Wrapper, dessen Kopierkonstruktor
ebenfalls nur gate_string_duplicate
einsetzt.
Hier liegt nämlich das Augenmerk auf Speicherschonung, denn wenn jemand einen nicht-verwalteten String einsetzt, soll das Framework diesen Typ nicht ungefragt in einen verwalteten konvertieren.
Es sollte daher in den höheren Ebenen stets eine Logik eingebaut werden, die nicht-verwaltete Strings zuerst klont oder kopiert, bevor die Daten langfristig weiter benutzt werden.
Fazit
Die Namensgebung ist … nunja, vielleicht nicht optimal. Tatsächlich habe ich mir schwer getan hier 3 Namen zu finden, die in etwa abdecken sollen, was wirklich passiert.
Ich gebe aber zu, dass man die Begriffe “duplizieren” und “klonen” auch genau anders herum interpretieren kann, wie ich es letztendlich entschieden habe.
Man kann es sich am besten so merken:
Duplikate sind quasi hirnlose maschinelle Kopien ( memcpy()
),
während Klonen echtes Leben eingehaucht wird und diese trotz genetischer
Verbindung zum Original eigene Wege gehen können.
Wie auch immer … egal wie der String erzeugt wurde, am Ende muss er immer
mit gate_string_release
freigegeben werden.