Sollte die STL bei großen Anwendungen vermieden werden?

24

Das hört sich vielleicht komisch an, aber in meiner Abteilung haben wir Probleme mit der folgenden Situation:

Wir arbeiten hier an einer Serveranwendung, die immer größer wird, auch wenn wir überlegen, sie in verschiedene Teile (DLL-Dateien) aufzuteilen, bei Bedarf dynamisch zu laden und anschließend zu entladen, um damit umgehen zu können die Leistungsprobleme.

Aber: Die von uns verwendeten Funktionen übergeben Eingabe- und Ausgabeparameter als STL-Objekte, und wie in einer Stack Overflow-Antwort erwähnt , ist dies eine sehr schlechte Idee. (Der Beitrag enthält einige ± Lösungen und Hacks, aber alles sieht nicht sehr solide aus.)

Natürlich könnten wir die Eingabe- / Ausgabeparameter durch Standard-C ++ - Typen ersetzen und STL-Objekte aus denen erstellen, die sich einmal in den Funktionen befinden. Dies könnte jedoch zu Leistungseinbußen führen.

Ist es in Ordnung zu folgern, dass Sie STL überhaupt nicht als Technologie verwenden dürfen, wenn Sie überlegen, eine Anwendung zu erstellen, die möglicherweise so groß wird, dass ein einzelner PC nicht mehr damit umgehen kann?

Weitere Hintergrundinformationen zu dieser Frage:
Es scheinen einige Missverständnisse in Bezug auf die Frage zu bestehen: Es handelt sich um das folgende Problem:
Meine Anwendung verbraucht sehr viel Leistung (CPU, Speicher), um ihre Arbeit abzuschließen, und ich möchte diese Arbeit aufteilen Da das Programm bereits in mehrere Funktionen unterteilt ist, ist es nicht so schwierig, einige DLLs aus meiner Anwendung zu erstellen und einige der Funktionen in die Exporttabelle dieser DLLs aufzunehmen. Dies würde zu folgender Situation führen:

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1 ist die auf Computer1 installierte Instanz der Anwendung, während App_Inst2 die auf Computer2 installierte Instanz derselben Anwendung ist.
DLL1.x ist eine DLL, die auf Machine1 installiert ist, während DLL2.x eine DLL ist, die auf Machine2 installiert ist.
DLLx.1 deckt die exportierte Funktion1 ab.
DLLx.2 deckt die exportierte Funktion2 ab.

Jetzt möchte ich auf Maschine1 Funktion1 und Funktion2 ausführen. Ich weiß, dass dies Machine1 überlasten wird, daher möchte ich eine Nachricht an App_Inst2 senden und diese Anwendungsinstanz auffordern, function2 auszuführen.

Die Eingabe- / Ausgabeparameter von function1 und function2 sind STL-Objekte (C ++ Standard Type Library), und ich erwarte, dass der Kunde regelmäßig Aktualisierungen von App_Inst1, App_Inst2, DLLx.y durchführt (aber nicht alle, der Kunde aktualisiert möglicherweise Machine1, aber nicht Machine2, oder aktualisieren Sie nur die Anwendungen, aber nicht die DLLs oder umgekehrt, ...). Wenn sich die Schnittstelle (Eingabe- / Ausgabeparameter) ändert, ist der Kunde offensichtlich gezwungen, vollständige Upgrades durchzuführen.

Wie in der angegebenen StackOverflow-URL erwähnt, kann jedoch eine einfache Neukompilierung von App_Inst1 oder einer der DLLs dazu führen, dass das gesamte System auseinanderfällt. Daher mein ursprünglicher Titel dieses Beitrags, in dem die Verwendung von STL (C ++ Standard Template) nicht empfohlen wird Bibliothek) für große Anwendungen.

Ich hoffe, dass ich hiermit einige Fragen / Zweifel ausgeräumt habe.

Dominique
quelle
44
Sind Sie sicher, dass Sie aufgrund Ihrer ausführbaren Größe Leistungsprobleme haben ? Können Sie einige Details darüber hinzufügen, ob es realistisch ist, anzunehmen, dass Ihre gesamte Software mit demselben Compiler kompiliert wurde (z. B. auf einmal auf dem Build-Server), oder ob Sie sich tatsächlich in unabhängige Teams aufteilen möchten?
Nvoigt
5
Grundsätzlich benötigen Sie eine Person, deren dedizierter Job "Build Manager" und "Release Manager" ist, um sicherzustellen, dass alle C ++ - Projekte auf derselben Compilerversion und mit identischen C ++ - Compilereinstellungen kompiliert werden, die aus einer konsistenten Momentaufnahme (Version) der Quelle kompiliert wurden Code usw. Typischerweise wird dies unter dem Motto "kontinuierliche Integration" erledigt. Wenn Sie online suchen, finden Sie viele Artikel und Tools. Veraltete Praktiken können sich selbst verstärken - eine veraltete Praktik kann dazu führen, dass alle Praktiken veraltet sind.
rwong
8
Die akzeptierte Antwort in der verknüpften Frage besagt, dass das Problem im Allgemeinen bei C ++ - Aufrufen liegt. "C ++, aber nicht STL" hilft also nicht, Sie müssen auf Nummer sicher gehen (aber auch die Antworten sehen, Serialisierung ist wahrscheinlich eine bessere Lösung).
Frax
52
Dynamisches Laden bei Bedarf und anschließendes Entladen, um die Leistungsprobleme bewältigen zu können Welche "Leistungsprobleme"? Ich kenne keine anderen Probleme als die Verwendung von zu viel Speicher, die durch das Entladen von Dlls aus dem Speicher behoben werden können - und wenn dies das Problem ist, ist die einfachste Lösung, einfach mehr RAM zu kaufen. Haben Sie Ihre Anwendung profiliert , um die tatsächlichen Leistungsengpässe zu ermitteln? Weil dies wie ein XY-Problem klingt - Sie haben nicht spezifizierte "Leistungsprobleme" und jemand hat sich bereits für die Lösung entschieden.
Andrew Henle
4
@MaxBarraclough "The STL" ist als alternativer Name für die in der C ++ Standard Library zusammengefassten Vorlagencontainer und Funktionen durchaus akzeptiert. Tatsächlich verweisen die C ++ - Kernrichtlinien, die von Bjarne Stroustrup und Herb Sutter geschrieben wurden, wiederholt auf "die STL", wenn sie darüber sprechen. Eine maßgeblichere Quelle kann man nicht finden.
Sean Burton

Antworten:

110

Dies ist ein eiskaltes klassisches XY-Problem.

Ihr eigentliches Problem sind Leistungsprobleme. Ihre Frage verdeutlicht jedoch, dass Sie keine Profilerstellung oder andere Bewertungen vorgenommen haben, um festzustellen, woher die Leistungsprobleme tatsächlich stammen. Stattdessen hoffen Sie, dass das Aufteilen Ihres Codes in DLLs das Problem auf magische Weise lösen wird (was für die Aufzeichnung nicht der Fall ist), und jetzt sind Sie besorgt über einen Aspekt dieser Nichtlösung.

Stattdessen müssen Sie das eigentliche Problem lösen. Wenn Sie mehrere ausführbare Dateien haben, überprüfen Sie, welche die Verlangsamung verursacht. Stellen Sie dabei sicher, dass Ihr Programm die gesamte Verarbeitungszeit in Anspruch nimmt und kein schlecht konfigurierter Ethernet-Treiber oder ähnliches. Beginnen Sie anschließend mit der Profilerstellung für die verschiedenen Aufgaben in Ihrem Code. Der hochpräzise Timer ist hier Ihr Freund. Die klassische Lösung besteht darin, die durchschnittlichen Verarbeitungszeiten und die Verarbeitungszeiten im ungünstigsten Fall für einen Teil des Codes zu überwachen.

Wenn Sie Daten haben, können Sie herausfinden, wie Sie mit dem Problem umgehen können, und dann können Sie herausfinden, wo Sie optimieren müssen.

Graham
quelle
54
"Stattdessen hoffen Sie, dass das Aufteilen Ihres Codes in DLLs das Problem auf magische Weise lösen wird (was für den Datensatz nicht der Fall ist)" - +1. Ihr Betriebssystem implementiert mit ziemlicher Sicherheit das Anforderungs-Paging, das genau dasselbe Ergebnis wie das Laden und Entladen von Funktionen in DLLs erzielt , und zwar nur automatisch, anstatt manuell eingreifen zu müssen. Auch wenn Sie besser vorhersagen können, wie lange ein Teil des Codes nach seiner Verwendung hängen bleiben soll als das virtuelle Speichersystem des Betriebssystems (was eigentlich unwahrscheinlich ist), speichert das Betriebssystem die DLL-Datei im Cache und macht Ihre Bemühungen trotzdem zunichte .
Jules
@Jules Siehe Update - sie haben klargestellt, dass die DLLs nur auf separaten Rechnern existieren, so dass ich vielleicht sehen kann, wie diese Lösung funktioniert. Es gibt jetzt allerdings Kommunikationsaufwand, so schwer zu wissen.
Izkata
2
@Izkata - es ist immer noch nicht ganz klar, aber ich denke, es wird beschrieben, dass sie dynamisch (basierend auf der Laufzeitkonfiguration) eine Version jeder Funktion auswählen möchten, die entweder lokal oder remote ist. Ein Teil der EXE-Datei, der niemals auf einem bestimmten Computer verwendet wird, wird jedoch niemals in den Arbeitsspeicher geladen. Daher ist die Verwendung von DLLs für diesen Zweck nicht erforderlich. Nehmen Sie einfach beide Versionen aller Funktionen in den Standardbuild auf und erstellen Sie eine Tabelle mit Funktionszeigern (oder C ++ - aufrufbaren Objekten oder einer beliebigen Methode, die Sie bevorzugen), um die entsprechende Version jeder Funktion aufzurufen.
Jules
38

Wenn Sie eine Software auf mehrere physische Maschinen aufteilen müssen, müssen Sie beim Übertragen von Daten zwischen Maschinen eine gewisse Form der Serialisierung verwenden, da Sie in einigen Fällen nur genau die gleiche Binärdatei zwischen Maschinen senden können. Die meisten Serialisierungsmethoden haben keine Probleme bei der Verarbeitung von STL-Typen, sodass mir dieser Fall keine Sorgen machen würde.

Wenn Sie eine Anwendung in Shared Libraries (DLLs) aufteilen müssen (bevor Sie dies aus Leistungsgründen tun, sollten Sie wirklich sicherstellen, dass es Ihre Leistungsprobleme tatsächlich lösen würde), kann die Übergabe von STL-Objekten ein Problem sein, muss es aber nicht. Wie der von Ihnen angegebene Link bereits beschreibt, funktioniert das Übergeben von AWL-Objekten, wenn Sie denselben Compiler und dieselben Compilereinstellungen verwenden. Wenn Benutzer die DLLs bereitstellen, können Sie sich möglicherweise nicht ohne Weiteres darauf verlassen. Wenn Sie jedoch alle DLLs bereitstellen und alles zusammen kompilieren, können Sie sich möglicherweise darauf verlassen und STL-Objekte über DLL-Grenzen hinweg verwenden. Sie müssen immer noch auf Ihre Compilereinstellungen achten, damit Sie nicht mehrere verschiedene Heaps erhalten, wenn Sie den Objektbesitz übergeben, obwohl dies kein STL-spezifisches Problem ist.

Pierre Andersson
quelle
1
Ja, und insbesondere der Teil über das Übergeben von zugewiesenen Objekten über DLL / so-Grenzen hinweg. Im Allgemeinen besteht die einzige Möglichkeit, das Mehrfachzuweisungsproblem zu vermeiden, darin, sicherzustellen, dass die DLL (oder Bibliothek!), Die die Struktur zugewiesen hat, diese ebenfalls freigibt. Aus diesem Grund sehen Sie viele, viele C-APIs, die so geschrieben sind: eine explizite kostenlose API für jede API, die ein zugewiesenes Array / eine zugewiesene Struktur zurückgibt. Das zusätzliche Problem bei STL ist, dass der Aufrufer möglicherweise erwarten kann, die übergebene komplexe Datenstruktur zu ändern (Elemente hinzufügen / entfernen), und dass dies ebenfalls nicht zulässig ist. Aber es ist schwer durchzusetzen.
Davidbak
1
Wenn ich eine Anwendung wie diese aufteilen müsste, würde ich wahrscheinlich COM verwenden, aber dies erhöht im Allgemeinen die Codegröße, da jede Komponente ihre eigenen C- und C ++ - Bibliotheken mitbringt (die gemeinsam genutzt werden können, wenn sie gleich sind, aber bei Bedarf abweichen können, Ich bin jedoch nicht davon überzeugt, dass dies die richtige Vorgehensweise für das OP-Problem ist
Simon Richter,
2
Als spezielles Beispiel ist es sehr wahrscheinlich, dass das Programm irgendwo Text an eine andere Maschine senden möchte. Irgendwann wird es einen Zeiger auf einige Zeichen geben, die an der Darstellung dieses Textes beteiligt sind. Sie können absolut nicht einfach die Bits dieser Zeiger übertragen und ein definiertes Verhalten auf der Empfängerseite erwarten
Caleth
20

Wir arbeiten hier an einer Serveranwendung, die immer größer wird, auch wenn wir überlegen, sie in verschiedene Teile (DLLs) aufzuteilen, die bei Bedarf dynamisch geladen und anschließend entladen werden, um die Probleme zu lösen Performance-Probleme

RAM ist billig und daher ist inaktiver Code billig. Das Laden und Entladen von Code (insbesondere das Entladen) ist ein fragiler Prozess und hat wahrscheinlich keine wesentlichen Auswirkungen auf die Leistung Ihres Programms auf moderner Desktop- / Server-Hardware.

Cache ist teurer, wirkt sich jedoch nur auf Code aus, der kürzlich aktiv war, und nicht auf Code, der nicht im Arbeitsspeicher verwendet wurde.

Im Allgemeinen wachsen Programme aufgrund der Datengröße oder der CPU-Zeit aus ihren Computern heraus, nicht aufgrund der Codegröße. Wenn Ihre Code-Größe so groß wird, dass es zu großen Problemen kommt, möchten Sie wahrscheinlich wissen, warum dies überhaupt geschieht.

Aber: Die von uns verwendeten Funktionen übergeben Eingabe- und Ausgabeparameter als STL-Objekte, und wie in dieser StackOverflow-URL erwähnt, ist dies eine sehr schlechte Idee.

Es sollte in Ordnung sein, solange die DLLs und die ausführbare Datei alle mit demselben Compiler erstellt und dynamisch mit derselben C ++ - Laufzeitbibliothek verknüpft sind. Daraus folgt, dass es kein Problem sein sollte, wenn die Anwendung und die zugehörigen DLLs als einzelne Einheit erstellt und bereitgestellt werden.

Problematisch wird es, wenn die Bibliotheken von verschiedenen Personen erstellt oder separat aktualisiert werden können.

Ist es in Ordnung zu folgern, dass Sie STL überhaupt nicht als Technologie verwenden dürfen, wenn Sie überlegen, eine Anwendung zu erstellen, die möglicherweise so groß wird, dass ein einzelner PC nicht mehr damit umgehen kann?

Nicht wirklich.

Sobald Sie damit beginnen, eine Anwendung auf mehrere Computer zu verteilen, müssen Sie eine ganze Reihe von Überlegungen anstellen, wie Sie die Daten zwischen diesen Computern übertragen. Die Details, ob STL-Typen oder einfachere Typen verwendet werden, gehen wahrscheinlich im Rauschen verloren.

Peter Green
quelle
2
Inaktiver Code wird wahrscheinlich gar nicht erst in den Arbeitsspeicher geladen. Die meisten Betriebssysteme laden Seiten nur dann von ausführbaren Dateien, wenn sie tatsächlich benötigt werden.
Jules
1
@Jules: Wenn toter Code mit Live-Code (mit einer Seitengröße von 4 KB) gemischt wird, wird er zugeordnet und geladen. Cache arbeitet mit einer viel feineren (64B) Granularität, so dass es immer noch meistens zutrifft, dass nicht verwendete Funktionen nicht viel schaden. Jede Seite benötigt jedoch einen TLB-Eintrag und (im Gegensatz zum RAM) eine knappe Laufzeitressource. (Bei dateibasierten Zuordnungen werden normalerweise keine Riesen-Seiten verwendet, zumindest nicht unter Linux. Eine Riesen-Seite ist 2 MB bei x86-64, sodass Sie viel mehr Code oder Daten abdecken können, ohne TLB-Fehler bei Riesen-Seiten zu erhalten.)
Peter Cordes
1
Was @PeterCordes merkt: Stellen Sie also sicher, dass Sie "PGO" als Teil Ihres Build-for-Release-Prozesses verwenden!
JDługosz
13

Nein, ich denke nicht, dass diese Schlussfolgerung folgt. Auch wenn Ihr Programm auf mehrere Maschinen verteilt ist, besteht kein Grund, dass Sie durch die interne Verwendung der STL gezwungen sind, sie für die Kommunikation zwischen Modulen und Prozessen zu verwenden.

Tatsächlich würde ich argumentieren, dass Sie das Design externer Schnittstellen von Anfang an von der internen Implementierung trennen sollten, da erstere solider / schwerer zu ändern sein werden als das, was intern verwendet wird

Bwmat
quelle
7

Sie verpassen den Punkt dieser Frage.

Grundsätzlich gibt es zwei Arten von DLLs. Deine eigene und die von jemand anderem. Das "STL-Problem" besteht darin, dass Sie und sie möglicherweise nicht denselben Compiler verwenden. Dies ist natürlich kein Problem für Ihre eigene DLL.

MSalters
quelle
5

Wenn Sie die DLLs gleichzeitig mit demselben Compiler und denselben Erstellungsoptionen aus demselben Quellbaum erstellen, funktioniert dies in Ordnung.

Die "Windows-gewürzte" Art, eine Anwendung in mehrere Teile aufzuteilen, von denen einige wiederverwendbar sind, sind jedoch COM-Komponenten . Dies können kleine (einzelne Steuerelemente oder Codecs) oder große (IE ist als COM-Steuerelement in mshtml.dll verfügbar) sein.

Bei Bedarf dynamisch laden und anschließend entladen

Für eine Serveranwendung wird dies wahrscheinlich eine schreckliche Effizienz haben. Dies ist nur dann wirklich sinnvoll, wenn Sie eine Anwendung haben, die über einen langen Zeitraum mehrere Phasen durchläuft, sodass Sie wissen, wann etwas nicht mehr benötigt wird. Es erinnert mich an DOS-Spiele mit dem Overlay-Mechanismus.

Wenn Ihr virtuelles Speichersystem ordnungsgemäß funktioniert, übernimmt es dies für Sie, indem es nicht verwendete Codepages auslagert.

kann so groß werden, dass ein einzelner PC nicht mehr damit umgehen kann

Kaufen Sie einen größeren PC.

Vergessen Sie nicht, dass ein Laptop mit der richtigen Optimierung einen Hadoop-Cluster übertreffen kann.

Wenn Sie wirklich mehrere Systeme benötigen, müssen Sie sich die Grenzen zwischen ihnen genau überlegen, da dort die Serialisierungskosten anfallen. Hier sollten Sie sich mit Frameworks wie MPI befassen.

pjc50
quelle
1
"Es ist nur dann wirklich sinnvoll, wenn Sie eine Anwendung haben, die über einen langen Zeitraum mehrere Phasen durchläuft, sodass Sie wissen, wann etwas nicht mehr benötigt wird." Zwischenspeichern Sie die DLL-Dateien, was wahrscheinlich mehr Speicherplatz in Anspruch nimmt, als nur die Funktionen direkt in Ihre ausführbare Basisdatei aufzunehmen. Overlays sind nur in Systemen ohne virtuellen Speicher nützlich oder wenn der virtuelle Adressraum der begrenzende Faktor ist (ich nehme an, diese Anwendung ist 64-Bit, nicht 32 ...).
Jules
3
"Kaufen Sie einen größeren PC" +1. Sie können jetzt Systeme mit mehreren Terabyte RAM erwerben . Sie können einen von Amazon für weniger als den Stundensatz eines einzelnen Entwicklers mieten. Wie viel Entwicklerzeit werden Sie in die Optimierung Ihres Codes investieren, um die Speichernutzung zu reduzieren?
Jules
2
Das größte Problem, mit dem ich beim Kauf eines größeren PCs konfrontiert war, betraf die Frage, wie weit sich Ihre App skalieren lässt. Meine Antwort lautete: "Wie viel sind Sie bereit, für einen Test auszugeben? Ich gehe davon aus, dass er so hoch ist, dass das Mieten einer geeigneten Maschine und das Einrichten eines angemessen großen Tests Tausende von Dollar kosten wird. Keiner unserer Kunden ist in der Nähe." was ein PC mit einer CPU kann. " Viele ältere Programmierer haben keine realistische Vorstellung davon, wie viele PCs erwachsen geworden sind. Die Videokarte allein in modernen PCs ist nach Maßstäben des 20. Jahrhunderts ein Supercomputer.
MSalters
COM-Komponenten? In den neunziger Jahren vielleicht, aber jetzt?
Peter Mortensen
@MSalters - richtig ... Wer Fragen dazu hat, wie weit eine Anwendung auf einem einzelnen PC skaliert werden kann, sollte sich die Spezifikationen für den Instanztyp Amazon EC2 x1e.32xlarge ansehen - insgesamt 72 physische Prozessorkerne auf dem Computer mit 128 virtuellen Kernen 2,3 GHz (Burst-fähig bis 3,1 GHz), möglicherweise bis zu 340 GB / s Speicherbandbreite (abhängig von der Art des installierten Speichers, der in der Spezifikation nicht beschrieben ist) und 3,9 TB RAM. Der Cache reicht aus, um die meisten Anwendungen auszuführen, ohne jemals den Hauptspeicher zu berühren. Auch ohne GPU ist es so leistungsfähig wie ein Supercomputer-Cluster mit 500 Knoten aus dem Jahr 2000.
Jules
0

Wir arbeiten hier an einer Serveranwendung, die immer größer wird, auch wenn wir überlegen, sie in verschiedene Teile (DLL-Dateien) aufzuteilen, bei Bedarf dynamisch zu laden und anschließend zu entladen, um damit umgehen zu können die Leistungsprobleme.

Der erste Teil ist sinnvoll (Aufteilung der Anwendung auf verschiedene Maschinen aus Leistungsgründen).

Der zweite Teil (Laden und Entladen von Bibliotheken) macht keinen Sinn, da dies zusätzlichen Aufwand bedeutet und die Dinge nicht (wirklich) verbessert.

Das von Ihnen beschriebene Problem lässt sich besser mit dedizierten Rechenmaschinen lösen, diese sollten jedoch nicht mit derselben (Haupt-) Anwendung funktionieren.

Die klassische Lösung sieht folgendermaßen aus:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

Zwischen dem Front-End und den Rechenmaschinen stehen möglicherweise zusätzliche Funktionen wie Lastausgleich und Leistungsüberwachung zur Verfügung, und die spezielle Verarbeitung auf dedizierten Maschinen ist gut für die Caching- und Durchsatzoptimierung geeignet.

Dies impliziert in keiner Weise ein zusätzliches Laden / Entladen von DLLs oder irgendetwas, das mit der STL zu tun hat.

Das heißt, verwenden Sie STL nach Bedarf intern und serialisieren Sie Ihre Daten zwischen den Elementen (siehe grpc- und Protokollpuffer und die Art der von ihnen gelösten Probleme).

Dies sagte, mit den begrenzten Informationen, die Sie zur Verfügung gestellt haben, sieht dies wie das klassische xy-Problem aus (wie @Graham sagte).

utnapistim
quelle