Wie hoch ist der Overhead von intelligenten Zeigern im Vergleich zu normalen Zeigern in C ++ 11? Mit anderen Worten, wird mein Code langsamer, wenn ich intelligente Zeiger verwende, und wenn ja, wie viel langsamer?
Insbesondere frage ich nach C ++ 11 std::shared_ptr
und std::unique_ptr
.
Offensichtlich wird das Material, das auf den Stapel geschoben wird, größer sein (zumindest denke ich das), da ein intelligenter Zeiger auch seinen internen Status (Referenzanzahl usw.) speichern muss. Die Frage ist wirklich, wie viel dies kosten wird meine Leistung beeinflussen, wenn überhaupt?
Zum Beispiel gebe ich einen intelligenten Zeiger von einer Funktion anstelle eines normalen Zeigers zurück:
std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();
Oder wenn beispielsweise eine meiner Funktionen einen intelligenten Zeiger als Parameter anstelle eines normalen Zeigers akzeptiert:
void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);
quelle
std::unique_ptr
oderstd::shared_ptr
?Antworten:
std::unique_ptr
hat nur dann Speicheraufwand, wenn Sie ihn mit einem nicht trivialen Deleter versehen.std::shared_ptr
hat immer Speicher-Overhead für Referenzzähler, obwohl es sehr klein ist.std::unique_ptr
Zeitaufwand nur während des Konstruktors (wenn der bereitgestellte Deleter kopiert und / oder der Zeiger auf Null initialisiert werden muss) und während des Destruktors (um das eigene Objekt zu zerstören).std::shared_ptr
hat Zeitaufwand im Konstruktor (um den Referenzzähler zu erstellen), im Destruktor (um den Referenzzähler zu dekrementieren und möglicherweise das Objekt zu zerstören) und im Zuweisungsoperator (um den Referenzzähler zu erhöhen). Aufgrund der Gewindesicherheitsgarantien vonstd::shared_ptr
sind diese Inkremente / Dekremente atomar und erhöhen somit den Overhead.Beachten Sie, dass keiner von ihnen Zeitaufwand beim Dereferenzieren hat (beim Abrufen des Verweises auf das eigene Objekt), während dieser Vorgang für Zeiger am häufigsten zu sein scheint.
Zusammenfassend lässt sich sagen, dass es einen gewissen Overhead gibt, der den Code jedoch nicht verlangsamen sollte, es sei denn, Sie erstellen und zerstören kontinuierlich intelligente Zeiger.
quelle
unique_ptr
hat keinen Overhead im Destruktor. Es funktioniert genauso wie bei einem rohen Zeiger.std::unique_ptr
? Wenn Sie ein erstellenstd::unique_ptr<int>
, wird das interneint*
initialisiert,nullptr
ob es Ihnen gefällt oder nicht.Wie bei jeder Codeleistung besteht das einzige wirklich zuverlässige Mittel, um harte Informationen zu erhalten , darin, den Maschinencode zu messen und / oder zu überprüfen .
Das heißt, einfache Argumentation sagt das
Bei Debug-Builds ist mit einem gewissen Overhead zu rechnen, da z. B.
operator->
als Funktionsaufruf ausgeführt werden muss, damit Sie darauf zugreifen können (dies ist wiederum auf die generelle mangelnde Unterstützung für das Markieren von Klassen und Funktionen als Nicht-Debug zurückzuführen).Denn
shared_ptr
bei der ersten Erstellung können Sie mit einem gewissen Overhead rechnen, da dies die dynamische Zuweisung eines Steuerblocks umfasst und die dynamische Zuweisung sehr viel langsamer ist als jede andere grundlegende Operation in C ++ (verwenden Sie diese,make_shared
wenn dies praktisch möglich ist, um diesen Overhead zu minimieren).Auch für die
shared_ptr
Aufrechterhaltung eines Referenzzählers gibt es einen minimalen Overhead, z. B. beim Übergeben einesshared_ptr
By-Werts, aber es gibt keinen solchen Overhead fürunique_ptr
.Wenn Sie den ersten Punkt oben berücksichtigen, tun Sie dies beim Messen sowohl für Debug- als auch für Release-Builds.
Das internationale C ++ - Standardisierungskomitee hat einen technischen Leistungsbericht veröffentlicht , der jedoch bereits 2006 veröffentlicht wurde
unique_ptr
undshared_ptr
der Standardbibliothek hinzugefügt wurde. Trotzdem waren intelligente Zeiger zu diesem Zeitpunkt ein alter Hut, daher berücksichtigte der Bericht auch dies. Zitieren des relevanten Teils:Als fundierte Vermutung wurde mit den heute beliebtesten Compilern ab Anfang 2014 das „Gut auf dem neuesten Stand der Technik“ erreicht.
quelle
Meine Antwort unterscheidet sich von den anderen und ich frage mich wirklich, ob sie jemals Code profiliert haben.
shared_ptr hat aufgrund seiner Speicherzuweisung für den Steuerblock (der den Referenzzähler und eine Zeigerliste für alle schwachen Referenzen enthält) einen erheblichen Aufwand für die Erstellung. Aufgrund dessen und der Tatsache, dass std :: shared_ptr immer ein 2-Zeiger-Tupel ist (eines für das Objekt, eines für den Steuerblock), hat es auch einen enormen Speicheraufwand.
Wenn Sie einen shared_pointer als Wertparameter an eine Funktion übergeben, ist dieser mindestens zehnmal langsamer als ein normaler Aufruf und erstellt im Codesegment viele Codes für das Abwickeln des Stapels. Wenn Sie es als Referenz übergeben, erhalten Sie eine zusätzliche Indirektion, die auch in Bezug auf die Leistung ziemlich schlechter sein kann.
Deshalb sollten Sie dies nur tun, wenn die Funktion wirklich in die Eigentümerverwaltung involviert ist. Andernfalls verwenden Sie "shared_ptr.get ()". Es soll nicht sicherstellen, dass Ihr Objekt während eines normalen Funktionsaufrufs nicht getötet wird.
Wenn Sie verrückt werden und shared_ptr für kleine Objekte wie einen abstrakten Syntaxbaum in einem Compiler oder für kleine Knoten in einer anderen Diagrammstruktur verwenden, werden Sie einen enormen Leistungsabfall und einen enormen Speicherzuwachs feststellen. Ich habe ein Parser-System gesehen, das kurz nach dem Markteintritt von C ++ 14 und bevor der Programmierer den korrekten Umgang mit intelligenten Zeigern lernte, neu geschrieben wurde. Das Umschreiben war eine Größenordnung langsamer als der alte Code.
Es ist keine Silberkugel und rohe Zeiger sind per Definition auch nicht schlecht. Schlechte Programmierer sind schlecht und schlechtes Design ist schlecht. Entwerfen Sie mit Sorgfalt, entwerfen Sie unter Berücksichtigung klarer Eigentumsverhältnisse und versuchen Sie, shared_ptr hauptsächlich an der Subsystem-API-Grenze zu verwenden.
Wenn Sie mehr erfahren möchten, können Sie Nicolai M. Josuttis guten Vortrag über "Der reale Preis gemeinsamer Zeiger in C ++" https://vimeo.com/131189627 ansehen.
Er geht tief in die Implementierungsdetails und die CPU-Architektur für atomare Schreibbarrieren ein Schlösser usw. Wenn Sie einmal zugehört haben, werden Sie nie davon sprechen, dass diese Funktion billig ist. Wenn Sie nur einen Beweis für die langsamere Größe wünschen, überspringen Sie die ersten 48 Minuten und beobachten Sie, wie er Beispielcode ausführt, der bis zu 180-mal langsamer (kompiliert mit -O3) ausgeführt wird, wenn Sie überall einen gemeinsam genutzten Zeiger verwenden.
quelle
std::make_shared()
? Außerdem finde ich Demonstrationen von offensichtlichem Missbrauch etwas langweilig ...Mit anderen Worten, wird mein Code langsamer, wenn ich intelligente Zeiger verwende, und wenn ja, wie viel langsamer?
Langsamer? Höchstwahrscheinlich nicht, es sei denn, Sie erstellen mit shared_ptrs einen riesigen Index und haben nicht genügend Speicher, bis Ihr Computer faltig wird, wie wenn eine alte Dame von einer unerträglichen Kraft aus der Ferne zu Boden stürzt.
Was Ihren Code langsamer machen würde, sind langsame Suchvorgänge, unnötige Schleifenverarbeitung, riesige Kopien von Daten und viele Schreibvorgänge auf die Festplatte (wie Hunderte).
Die Vorteile eines intelligenten Zeigers hängen alle mit der Verwaltung zusammen. Aber ist der Aufwand notwendig? Dies hängt von Ihrer Implementierung ab. Angenommen, Sie iterieren über ein Array von 3 Phasen. Jede Phase verfügt über ein Array von 1024 Elementen. Das Erstellen eines
Aber willst du das wirklich tun?smart_ptr
für diesen Prozess ist möglicherweise übertrieben, da Sie nach Abschluss der Iteration wissen, dass Sie es löschen müssen. Sie können also zusätzlichen Speicher gewinnen, wenn Sie keinesmart_ptr
...Ein einzelner Speicherverlust kann dazu führen, dass Ihr Produkt zeitlich ausfällt (sagen wir, Ihr Programm verliert 4 Megabyte pro Stunde, es würde Monate dauern, bis ein Computer kaputt geht, aber es wird kaputt gehen, Sie wissen es, weil das Leck da ist). .
Ist wie zu sagen "Ihre Software ist für 3 Monate garantiert, dann rufen Sie mich für den Service."
Am Ende geht es also wirklich darum ... können Sie mit diesem Risiko umgehen? Wenn Sie einen Rohzeiger verwenden, um Ihre Indizierung über Hunderte verschiedener Objekte durchzuführen, sollten Sie die Kontrolle über den Speicher verlieren.
Wenn die Antwort Ja lautet, verwenden Sie einen Rohzeiger.
Wenn Sie nicht einmal darüber nachdenken möchten,
smart_ptr
ist a eine gute, praktikable und großartige Lösung.quelle
smart_ptr
sind wirklich nützlich für große TeamsThats why you should not do this unless the function is really involved in ownership management
... großartige Antwort, danke, positiv bewertetNur für einen kurzen Blick und nur für den
[]
Bediener ist es ~ 5X langsamer als der Rohzeiger, wie im folgenden Code gezeigt, der mitgcc -lstdc++ -std=c++14 -O0
diesem Ergebnis kompiliert und ausgegeben wurde:Ich fange an, C ++ zu lernen. Ich habe Folgendes im Kopf: Sie müssen immer wissen, was Sie tun, und sich mehr Zeit nehmen, um zu wissen, was andere in Ihrem C ++ getan haben.
BEARBEITEN
Wie von @Mohan Kumar angegeben, habe ich weitere Details angegeben. Die gcc-Version lautet
7.4.0 (Ubuntu 7.4.0-1ubuntu1~14.04~ppa1)
: Das obige Ergebnis wurde erhalten, wenn das-O0
verwendet wird. Wenn ich jedoch das '-O2'-Flag verwende, habe ich Folgendes erhalten:Dann verschoben zu
clang version 3.9.0
,-O0
war:-O2
war:Das Ergebnis von Clang
-O2
ist erstaunlich.quelle
-O0
Code vergleichen oder debuggen. Die Ausgabe wird äußerst ineffizient sein . Verwenden Sie immer mindestens-O2
(oder-O3
heutzutage, weil einige Vektorisierungen nicht durchgeführt werden-O2
)free
Aufruf in den Malloc-Test aufnehmen unddelete[]
für new (oder variablea
statisch machen), da dieunique_ptr
sdelete[]
unter der Haube in ihren Destruktoren aufrufen .