So implementieren Sie eine Zeichenfolge, die ausschließlich auf dem Stapel zugeordnet wird

8

In einem Projekt vor etwa zehn Jahren haben wir festgestellt, dass die dynamischen Zuweisungen von std::vector zu einem erheblichen Leistungsverlust geführt haben. In diesem Fall wurden viele kleine Vektoren zugewiesen, so dass die schnelle Lösung darin bestand, eine vektorartige Klasse zu schreiben, die ein stapelbasiertes vor-zugeordnetes char -Array umgab, das als roher Speicher für seine Kapazität verwendet wurde. Das Ergebnis war ein static_vector<typename T, std::size_t Max> . So etwas ist leicht genug zu schreiben, wenn Sie ein paar Grundlagen kennen, und Sie finden einige solche Bestien im Internet . Tatsächlich hat boost jetzt auch einen .

Wenn wir jetzt an einer eingebetteten Plattform arbeiten, benötigen wir ein static_basic_string . Das wäre eine Zeichenfolge, die eine feste maximale Speichermenge auf dem Stapel vorverteilt und diese als ihre Kapazität verwendet.

Zuerst dachte ich, das sollte ziemlich einfach sein (es könnte immerhin auf der vorhandenen static_vector basieren), aber wenn ich noch einmal auf die Schnittstelle von std::basic_string schaue, bin ich mir nicht mehr so ​​sicher. Es ist viel komplexer als die Schnittstelle std::vector . Besonders die Implementierung der Familie von find() functions std::basic_string ist mehr als nur eine mühsame Aufgabe.

Das hat mich wieder zum Nachdenken gebracht. Denn dafür wurden Allokatoren geschaffen: Ersetzen Sie die Allokation basierend auf new und delete mit anderen Mitteln. Zu sagen, dass die Allokator-Schnittstelle unhandlich ist, wäre jedoch eine Untertreibung. Es gibt ein paar Artikel, die es erklären, aber es gibt einen Grund, warum ich in den letzten 15 Jahren so viele selbstgewählte Allokatoren gesehen habe.

Also hier ist meine Frage:

Wenn Sie ein basic_string Lookalike implementieren müssten, wie würden Sie das machen?

  • Schreiben Sie Ihre eigene static_basic_string ?
  • Schreiben Sie einen Zuordner, der an std::basic_string ?
  • übergeben wird
  • Etwas tun, an das ich nicht gedacht habe?
  • Benutze etwas von Boost, von dem ich nichts weiß?

Wie immer gibt es eine ziemlich wichtige Einschränkung für uns: Da wir auf einer eingebetteten Plattform arbeiten, sind wir an GCC 4.1.2 gebunden, so dass wir nur C ++ 03, TR1 und Boost 1.52 einsetzen können / sup>

    
sbi 14.10.2014, 08:47
quelle

7 Antworten

4

Die erste Frage ist: Wie viel von der extra Schnittstelle brauchst du? benutzen? Die meisten von std::string können zusätzliche Schnittstellen sein trivial implementiert mit Funktionen in <algorithm> (z. std::find , std::find_if und std::search ), und in vielen Fälle, es gibt große Teile davon, die sowieso nicht verwendet werden. Implementieren Sie es einfach nach Bedarf.

Ich denke nicht, dass Sie dies mit einem benutzerdefinierten Zuordner machen können. Der einzige Weg, um den Speicher "on stack" zu bekommen, wäre es zu deklarieren als Mitglied des benutzerdefinierten Zuordners, der alle erstellen würde Arten von Problemen beim Kopieren von ihnen. Und Allokatoren müssen sein kopierbar, und die Kopien müssen idempotent sein.

Vielleicht finden Sie eine kostenlose Implementierung im Netz von std::string , das die kleine Zeichenfolgenimplementierung verwendet; dann modifiziere es so, dass die Cutoff-Größe (darüber hinaus dynamisch) verwendet wird Zuweisung) ist größer als alle Strings, die Sie tatsächlich verwenden. (Dort sind mehrere Open-Source-Implementierungen der Standardbibliothek verfügbar; der mit g ++ gelieferte verwendet immer noch COW, aber Ich vermute, dass die meisten anderen SSO verwenden.)

    
James Kanze 14.10.2014, 09:53
quelle
1

Es ist einfach, schreibe einen Stapelzuordner, hier ist ein Beispiel:

Ссылка

Mit Zuordnern können Sie ebenso einfach z. B. aus einer speicherabgebildeten Datei, d. h. aus dem Laufwerk, oder aus einem statischen Array von char s zuordnen.

    
user1095108 14.10.2014 09:18
quelle
1

LLVM ADT verfügt über die Klasse SmallString . Es hat auch SmallVector und viele andere nützliche Klassen.

Während die aktuelle LLVM-Codebasis in Richtung C ++ 11 wandert, unterstützen (nicht so-) alte Versionen von LLVM C ++ 03.

    
Abyx 14.10.2014 09:42
quelle
1

Ein ausgezeichneter Ausgangspunkt ist Alexandrescus richtlinienbasierte String-Klasse, die in diesem Dr. Dobbs beschrieben wird Artikel . Es enthält eine SSO-Richtlinie, die grundsätzlich das tut, was Sie wollen (suchen Sie die Seite nach SmallStringOpt ) und ist leicht zu ändern, wenn / wie Sie es für notwendig halten. Es ist älter als C ++ 11, also geht es dir auch gut.

    
Tony Delroy 14.10.2014 11:04
quelle
1

Es gibt viele basic_string -Implementierungen, einige vollständig basierend auf dynamischer Zuweisung, einige andere auf dynamische Zuweisung nur für Strings, die breiter als eine gegebene Länge sind (tatsächlich verwenden sie ihren eigenen internen Puffer, wenn es passt).

Die Verwendung eines Zuordners ist wahrscheinlich nicht der richtige Weg, da die Schnittstelle zwischen der Zeichenfolge und dem Zuordner davon ausgeht, dass das Zuordnungsobjekt Teil des Containers ist, der zugewiesene Speicher jedoch von außerhalb des Containers selbst stammt. Sie können es arrangieren, indem Sie einen Allokator mit POSIX alloca implementieren, mit allen Nachteilen .

Das Problem beim Implementieren von Strings auf Stack ist, dass man es nicht dynamisch wachsen lassen kann (vielleicht hat der Stack zu diesem Zeitpunkt etwas mehr), aber man muss auch auf Operationen wie += achten, die einen String länger machen können und länger.

Sie enden also mit der Vorabzuweisung (als ein Array oder als zugeordneter Puffer, innerhalb Ihrer Klasse oder innerhalb eines Zuordners ändert sich das Problem nicht) eine Menge an Bytes, die Sie entweder verschwenden, aber nicht alle füllen, oder indem Sie sie nicht verwenden, wenn die Zeichenfolge zu groß geworden ist und dynamisch sein muss.

Es gibt wahrscheinlich einen Kompromiss zwischen dem Speicher-zu-Cache-Kommunikationsprozess (normalerweise mit 128 Byte oder 4 KByte), aber es ist stark hardwareabhängig, so dass sich die Komplexität nicht leisten wird.

Eine günstigere Lösung kann ein Zuordner sein, der immer noch auf dem Heap allokiert, aber die zurückgegebenen Blöcke behalten und wiederverwenden kann (bis zu einem bestimmten Limit), was die Notwendigkeit verringert, das System nach zugewiesen / freigegeben zu fragen.

In diesem Fall kann die Leistung jedoch nicht unbedingt von Vorteil sein, wenn das zugrundeliegende System new/delete bereits auf diese Weise implementiert.

    
Emilio Garavaglia 14.10.2014 09:32
quelle
0

Ich würde eine Kombination von implementierungsdefinierten VLAs und Standardalgorithmen verwenden, sollte ich denken.

    
quelle
-1

Dies ist ein funktionierender Code, aber NICHT DEN EMPFOHLENEN WEG .

Dieser Code hat viele Spuren, um zu zeigen, was er macht. Es überprüft nicht, dass die Größe der Zuordnungsanforderung den Puffer nicht überschreitet. Sie können dies überprüfen, wenn nötig. Beachten Sie, dass std :: basic_string versucht, mehr als nötig zuzuordnen.

%Vor%

Dieser Code wurde auf GCC und LLVM getestet und funktioniert wie erwartet (oder unerwartet).

Die Syntax ist unhandlich. Es ist nicht möglich, von basic_string abzuleiten und den Puffer einzubetten. Ein weitaus besserer Weg besteht darin, eine kleine spezialisierte buffer_string-Klasse mit der erforderlichen Teilmenge der basic_string-Schnittstelle zu erstellen.

    
ArunasR 14.10.2014 11:28
quelle