10 Stimmen

Geteilte Zeiger und die Leistung

Ich benutze seit einiger Zeit Shared Pointer, und ich habe Leistungsprobleme in meinem Programm... Daher würde ich gerne wissen, ob Shared Pointer zu einer Leistungsabnahme führen. Wenn ja, wie stark? Vielen Dank.

Mein Programm ist mehrfädig, und verwendet std::tr1::shared_ptr

13voto

pgast Punkte 1647

Wenn Ihre App XML-Nachrichten von etwa 700 Byte weitergibt, die in 65 Byte großen Google-Protokollnachrichten oder 85 Byte großen ASN.1-Nachrichten enthalten sein könnten, dann wird es wahrscheinlich keine Rolle spielen. Aber wenn es eine Million etwas pro Sekunde verarbeitet, würde ich die Kosten für das Hinzufügen von 2 kompletten Lese-/Änderungs-/Schreibzyklen beim Weitergeben eines Zeigers nicht außer Acht lassen.

Ein kompletter Lese-/Änderungs-/Schreibvorgang dauert etwa 50 ns, also sind zwei 100 ns. Dies sind die Kosten für einen Lock-Inkrement und ein Lock-Dekrement – das Gleiche wie bei 2 CAS-Operationen. Dies entspricht der Hälfte einer Reservierung und Freigabe eines Windows-Kritischen Abschnitts. Dies im Vergleich zu einem einzelnen Maschinenzyklus-Push (400 PICO-Sekunden auf einer 2,5-GHz-Maschine).

Und das beinhaltet nicht einmal die anderen Kosten für die Ungültigmachung der Cache-Zeile, die tatsächlich den Zähler enthält, die Auswirkungen der BUS-Sperre auf andere Prozessoren usw.

Das Übergeben von Smart Pointern per const-Referenz ist fast IMMER vorzuziehen. Wenn der Caller keinen neuen Shared Pointer erstellt, wenn er die Lebensdauer des Pointee garantieren oder kontrollieren möchte, dann handelt es sich um einen Fehler beim Caller. Einfach so thread-sichere Referenzzähl-Smart-Pointer per Wert weiterzugeben, ist einfach nur eine Einladung für Leistungseinbußen.

Die Verwendung von Referenzzählten Pointern vereinfacht zweifellos die Lebensdauer, aber shared Pointer per Wert weiterzugeben, um sich gegen Fehler beim Caller zu schützen, ist schlicht und ergreifend Unsinn.

Ein übermäßiger Einsatz von Referenzzählung kann schnell ein schlankes Programm, das 1 Million Nachrichten pro Sekunde verarbeiten kann, in ein fettes verwandeln, das auf derselben Hardware nur noch 150.000 Nachrichten pro Sekunde verarbeiten kann. Plötzlich benötigen Sie ein halbes Rack von Servern und 10.000 Dollar pro Jahr an Stromkosten.

Sie sind immer besser dran, wenn Sie die Lebensdauer Ihrer Objekte ohne Referenzzählung verwalten können.

Ein Beispiel für eine einfache Verbesserung ist, wenn Sie beispielsweise ein Objekt verzweigen möchten und Sie die Breite der Verzweigung (sagen wir n) kennen, dann inkrementieren Sie um n, anstatt bei jeder Verzweigung einzeln zu inkrementieren.

Übrigens, wenn die CPU ein Sperrpräfix sieht, denkt sie wirklich: "Oh nein, das wird wehtun."

All das gesagt habend stimme ich mit allen überein, dass Sie den Hotspot überprüfen sollten.

9voto

JaredPar Punkte 699699

Es ist nahezu unmöglich, diese Frage korrekt zu beantworten, wenn man die Daten berücksichtigt. Der einzige Weg, um herauszufinden, was die Leistungsprobleme in Ihrer Anwendung verursacht, besteht darin, ein Profiling-Tool auf das Programm laufen zu lassen und die Ausgabe zu untersuchen.

Abgesehen davon ist es sehr unwahrscheinlich, dass ein shared_ptr für die Verlangsamung verantwortlich ist. Der shared_ptr-Typ und viele frühe Eigenentwicklungen werden in einer immer größeren Anzahl von C++-Programmen verwendet. Ich selbst verwende sie in meiner Arbeit (beruflich und privat). Ich habe viel Zeit damit verbracht, meine Arbeitsanwendungen zu profilieren, und shared_ptr war in meinem Code oder in jedem anderen Code, der innerhalb der Anwendung ausgeführt wird, noch nie ein Problem. Es ist viel wahrscheinlicher, dass der Fehler woanders liegt.

5voto

Jerry Coffin Punkte 452852

Gemeinsame Zeiger werden referenziert gezählt. Besonders beim Einsatz von Mehrfädigkeit kann das Inkrementieren und Dekrementieren der Referenzzählung eine erhebliche Menge an Zeit in Anspruch nehmen. Der Grund, warum Mehrfädigkeit hier schädlich ist, ist, dass wenn Sie einen gemeinsamen Zeiger zwischen Threads übergeben haben, die Referenzzählung zwischen diesen Threads geteilt würde, daher muss jede Manipulation zwischen den Threads synchronisiert werden. Das kann die Dinge ziemlich verlangsamen.

Bearbeitung: Für diejenigen, die sich darüber kümmern, wie viel langsamer die Thread-Sperrung einige recht einfache Operationen machen kann, siehe Herb Sutters Tests mit einigen Implementierungen von CoW Strings. Während seine Tests weit davon entfernt sind, perfekt zu sein (zum Beispiel testete er nur unter Windows), gibt es dennoch eine Vorstellung davon, welche Art von Verlangsamung Sie erwarten können. Für die meisten praktischen Zwecke können/könnten Sie sich ein CoW-String als etwas ähnliches wie ein shared_ptr vorstellen, mit vielen (irrelevanten) Memberfunktionen hinzugefügt.

5voto

Mike Dunlavey Punkte 39339

Wenn Ihr Programm anscheinend ein Leistungsproblem hat, ist es ganz natürlich, damit zu beginnen zu raten, woran das Problem liegen könnte, aber wenn Sie eine Wette platzieren möchten, ist es fast zu 100% etwas ganz anderes. Profiling kann das Problem finden. Dies ist die Methode, die ich verwende.

3voto

peterchen Punkte 39679

Sehr unwahrscheinlich - Sie müssten die meiste Zeit damit verbringen, Zeiger herumzureichen.

Die Auswirkung von shared ptrs ist normalerweise gering, und es ist schwer, überhaupt einen Grenzfall zu konstruieren, in dem sie zum Problem werden (vorausgesetzt, eine ordentliche Implementierung und ein ordentlich optimierender Compiler).

Auswirkungen von shared ptr:

  • erhöhte Speicherzuweisungsgröße.
    Das würde nur dann eine Rolle spielen, wenn Sie viele gemeinsame Zeiger auf sehr kleine Objekte haben (sagen wir, zig Millionen shared_ptr) und/oder nahe an einem Speicherlimit arbeiten. Es besteht ein geringes Potenzial für eine spürbare Leistungseinbuße, wenn die zusätzlichen Zuweisungen eine Cache/NUMA-Ebene innerhalb einer inneren Schleife überschreiten

  • erhöhte Anzahl von Zuweisungen
    shared_ptr weist ein Tracking-Objekt zu (Referenzzähler, Schwachzähler und Deleter). Dies setzt den Heap unter Druck und kann zu einem allgemeinen Rückgang führen, wenn Sie eine hohe Gesamtzahl von Zuweisungen und Freigaben haben.
    Kann vermieden werden, indem make_shared verwendet wird, um Referenz und Tracking-Objekt in einer einzigen Zuweisung unterzubringen

  • Referenzzählung
    erhöht die Kosten einer Kopie eines Zeigers. In einer Single-Thread-Anwendung würden Sie nur dann feststellen, dass Sie ohnehin die meiste Zeit mit dem Kopieren von Zeigern verbringen. In einer Mehrfadenanwendung müssen Sie immer noch eine hohe Konkurrenz um die gleichen Zeiger haben.
    Kosten der Kopie können an vielen Stellen vermieden werden, indem ein shared_ptr const & z.B. als Funktionsargument übergeben wird.

  • Dereferenzierung
    Die zusätzlichen Dereferenzierungskosten betragen in einem Release-Build eines optimierenden Compilers null. Debug-Builds entsprechen oft Funktionsaufrufen und zusätzlichen NULL-Überprüfungen. Dennoch müssten Sie insbesondere in Debug-Builds die meiste Zeit damit verbringen, Zeiger zu dereferenzieren, um einen Unterschied festzustellen.


Ohne zusätzliche Informationen können wir Ihnen nicht helfen. Sie müssen beschreiben, worum es sich bei den "Leistungsproblemen" handelt (allgemeine Trägheit, bestimmte Vorgänge dauern lange, viel Auslagerung), und einige Schlüsselzahlen angeben - was Ihre App macht, wie viele intelligente Zeiger es gibt, wie oft sie kopiert werden und welche anderen Operationen Sie neben dem Jonglieren mit intelligenten Zeigern durchführen.

Oder Sie lernen, Leistungsmonitor und/oder Profiler zu verwenden, um herauszufinden, was die Verlangsamungen verursacht und ob es bestimmte Engpässe gibt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X