Duplikate und Klone

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:

  1. 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.
  2. 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 Addresse 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.
  3. 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 verwalteteten 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 garkeine 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 weiterbenutzt 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.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!