Text-Double-Buffering
« | 01 Nov 2018 | »Als Entwickler freue ich mich natürlich sehr, dass meine Erfindung letztlich dann doch in den C++17 Standard Einzug gefunden hat.
OK, Spaß bei Seite … das haben wohl auch schon zehntausende Entwickler vor
mir gebraucht und darauf hin erfunden. Aber trotzdem erinnere ich mich noch
zurück an das Jahr 2007,
als mir bei der Arbeit mit
std::string
unmittelbar auffiel, dass dieses unglaublich wichtige Feature im Standard fehlte.
Ein string_view
repräsentiert einen Teil eines Strings. Während übliche
C-char arrays und std::string
s Null-terminiert als ganzes im Speicher liegen,
kann man sich einen View als einen Pointer an eine beliebige Stelle im String
und dazu eine Leselänge vorstellen.
(Natürlich kann man im Iterator-Style auch ein end_of_view
anstatt einer
length_of_view
)
Das hat einen großen Vorteil: Wir können aus einem großen Datenpuffer unzählige String-Views herausziehen.
Ein schönes Beispiel ist ein XML - Parser, der einen XML-Block in einen Baum aus Tag-Strings und Attribut-Strings aufbaut.
Klassische C++ Bibliotheken erzeugen für jeden Knoten im XML Baum einen
std::string
und allokieren sich bei großen Dokumenten zum Hugo.
Der String-View verweist immer nur auf Ausschnitte des Gesamtdokuments und ist somit unglaublich schneller mit dem Erstellen solcher Strukturen fertig.
Aber die Genialität von string_view
soll hier gar nicht das Thema sein.
Problem: APIs brauchen oft Null-terminierte Strings
Und das kann dann wieder etwas dumm werden, denn der View kann nicht irgendwo in der Mitte seines referenzierten Puffers eine Null reinschreiben.
Die einzige Lösung ist dann für solche Fälle die benötigten Daten in einen temporären Puffer zu kopieren und eben diesen Puffer mit der API zu nutzen.
Damit wir jetzt nicht unsere frisch gewonnene Allokierungsreduktion wieder
verlieren, nutze ich gerne eine doppelte Strategie:
Wenn möglich wird ein Stack
- Puffer genutzt, der nicht aufwendig allokiert werden muss und nur übergroße Textstücke kommen auf den Heap.
1void do_something(struct simple_string_view* view) 2{ 3 char localbuffer[4096]; 4 char* heapbuffer = NULL; 5 char* buffer; 6 7 if(view->length_of_view + 1 > sizeof(length_of_view)) 8 { 9 /* large buffer is allocated on heap */ 10 heapbuffer = malloc(view->length_of_view + 1); 11 buffer = heapbuffer; 12 } 13 else 14 { 15 /* small buffer is allocated on stack*/ 16 buffer = localbuffer; 17 } 18 memcpy(buffer, view->begin_of_view, view->length_of_view); 19 buffer[view->length_of_view] = 0; 20 21 call_my_null_terminated_function(buffer); 22 23 if(heapbuffer != NULL) 24 { 25 free(heapbuffer); 26 } 27}
Ganz ähnlich zur String Debatte zeichnet sich auch hier ab:
Viele Bibliotheken haben ihren string_view
schon teils vor 30 Jahren
erfunden und implementiert.
Dass C++ erst seit 2017 hier nachzieht, ist leider viel zu spät … aber trotzdem ein wichtiger Ansatz. Vielleicht schaffen wir es ja in den nächsten 20 Jahren unsere eigenen Implementierungen durch den Standard zu ersetzen.
However … nicht jede Implementierung ist perfekt. Und wir sprechen hier nur von C++ und nicht von reinem C, wo ich dieses Feature ebenfalls schmerzhaft vermisse.
Das GATE Projekt definiert eine C-String Klasse, die beides sein kann, eine String-View oder ein allokierter String-Puffer.