Angenommen, es gibt zwei Threads, die durch asynchrones Senden von Datennachrichten aneinander kommunizieren. Jeder Thread hat eine Art Nachrichtenwarteschlange.
Meine Frage ist sehr niedrig: Was kann erwartet werden, um den Speicher am effizientesten zu verwalten? Ich kann mir mehrere Lösungen vorstellen:
- Der Absender erstellt das Objekt über
new
. Empfängeranrufedelete
. - Speicherpooling (um den Speicher zurück zum Absender zu übertragen)
- Müllabfuhr (zB Boehm GC)
- (Wenn die Objekte klein genug sind) Kopieren Sie nach Wert, um die Heap-Zuordnung vollständig zu vermeiden
1) ist die naheliegendste Lösung, daher werde ich sie für einen Prototyp verwenden. Die Chancen stehen gut, dass es schon gut genug ist. Unabhängig von meinem spezifischen Problem frage ich mich jedoch, welche Technik am vielversprechendsten ist, wenn Sie die Leistung optimieren.
Ich würde erwarten, dass Pooling theoretisch das Beste ist, insbesondere weil Sie zusätzliches Wissen über den Informationsfluss zwischen den Threads verwenden können. Ich befürchte jedoch, dass es auch am schwierigsten ist, das Richtige zu finden. Viel Tuning ... :-(
Die Speicherbereinigung sollte später (nach Lösung 1) recht einfach hinzuzufügen sein, und ich würde erwarten, dass sie sehr gut funktioniert. Ich denke also, dass es die praktischste Lösung ist, wenn sich 1) als zu ineffizient herausstellt.
Wenn die Objekte klein und einfach sind, ist das Kopieren nach Wert möglicherweise am schnellsten. Ich befürchte jedoch, dass dies die Implementierung der unterstützten Nachrichten unnötig einschränkt, daher möchte ich dies vermeiden.
quelle
unique_ptr
, meinen Sie wohlshared_ptr
. Obwohl die Verwendung eines intelligenten Zeigers zweifellos für die Ressourcenverwaltung gut ist, ändert dies nichts an der Tatsache, dass Sie eine Form der Speicherzuweisung und -freigabe verwenden. Ich denke, diese Frage ist eher niedrig.Der größte Leistungseinbruch bei der Kommunikation eines Objekts von einem Thread zu einem anderen ist der Aufwand für das Ergreifen eines Schlosses. Dies liegt in der Größenordnung von mehreren Mikrosekunden, was deutlich mehr ist als die durchschnittliche Zeit, die ein Paar von
new
/delete
benötigt (in der Größenordnung von hundert Nanosekunden). Vernünftigenew
Implementierungen versuchen, das Sperren um fast jeden Preis zu vermeiden, um Leistungseinbußen zu vermeiden.Sie möchten jedoch sicherstellen, dass Sie keine Sperren ergreifen müssen, wenn Sie die Objekte von einem Thread zu einem anderen kommunizieren. Ich kenne zwei allgemeine Methoden, um dies zu erreichen. Beide arbeiten nur unidirektional zwischen einem Sender und einem Empfänger:
Verwenden Sie einen Ringpuffer. Beide Prozesse steuern einen Zeiger in diesen Puffer, einer ist der Lesezeiger, der andere ist der Schreibzeiger.
Der Absender prüft zuerst, ob Platz zum Hinzufügen eines Elements vorhanden ist, indem er die Zeiger vergleicht, fügt dann das Element hinzu und erhöht dann den Schreibzeiger.
Der Empfänger prüft durch Vergleichen der Zeiger, ob ein zu lesendes Element vorhanden ist, liest dann das Element und erhöht dann den Lesezeiger.
Die Zeiger müssen atomar sein, da sie von den Threads gemeinsam genutzt werden. Jeder Zeiger wird jedoch nur von einem Thread geändert, der andere benötigt nur Lesezugriff auf den Zeiger. Die Elemente im Puffer können selbst Zeiger sein, wodurch Sie Ihren Ringpuffer einfach auf eine Größe skalieren können, die den Absenderblock nicht bildet.
Verwenden Sie eine verknüpfte Liste, die immer mindestens ein Element enthält. Der Empfänger hat einen Zeiger auf das erste Element, der Sender hat einen Zeiger auf das letzte Element. Diese Zeiger werden nicht gemeinsam genutzt.
Der Absender erstellt einen neuen Knoten für die verknüpfte Liste und setzt seinen
next
Zeiger aufnullptr
. Anschließend wird dernext
Zeiger des letzten Elements aktualisiert , um auf das neue Element zu zeigen. Schließlich speichert es das neue Element in einem eigenen Zeiger.Der Empfänger überwacht den
next
Zeiger des ersten Elements, um festzustellen, ob neue Daten verfügbar sind. In diesem Fall wird das alte erste Element gelöscht, der eigene Zeiger auf das aktuelle Element verschoben und mit der Verarbeitung begonnen.In diesem Setup müssen die
next
Zeiger atomar sein, und der Absender muss sicherstellen, dass das vorletzte Element nicht dereferenziert wird, nachdem er seinennext
Zeiger gesetzt hat. Der Vorteil ist natürlich, dass der Absender niemals sperren muss.Beide Ansätze sind viel schneller als jeder sperrenbasierte Ansatz, erfordern jedoch eine sorgfältige Implementierung, um die richtigen Ergebnisse zu erzielen. Und natürlich erfordern sie native Hardware-Atomizität von Zeiger-Schreibvorgängen / -Ladungen; Wenn Ihre
atomic<>
Implementierung intern eine Sperre verwendet, sind Sie ziemlich zum Scheitern verurteilt.Wenn Sie mehrere Leser und / oder Autoren haben, sind Sie ziemlich zum Scheitern verurteilt: Sie können versuchen, ein Schema ohne Sperren zu entwickeln, aber es wird bestenfalls schwierig sein, es zu implementieren. Diese Situationen sind mit einem Schloss viel einfacher zu handhaben. Wenn Sie jedoch eine Sperre greifen, können Sie Sorgen um stoppen
new
/delete
Leistung.quelle