Ich verstehe, dass die Verwendung von RTTI einen Ressourcenhit darstellt, aber wie groß ist dieser? Überall, wo ich gesucht habe, heißt es nur, dass "RTTI teuer ist", aber keiner von ihnen gibt tatsächlich Benchmarks oder quantitative Daten an, die Speicher, Prozessorzeit oder Geschwindigkeit regeln.
Wie teuer ist RTTI? Ich könnte es auf einem eingebetteten System verwenden, auf dem ich nur 4 MB RAM habe, also zählt jedes Bit.
Bearbeiten: Gemäß der Antwort von S. Lott wäre es besser, wenn ich einbeziehe, was ich tatsächlich tue. Ich verwende eine Klasse, um Daten unterschiedlicher Länge zu übergeben, die unterschiedliche Aktionen ausführen können. Daher wäre es schwierig, dies nur mit virtuellen Funktionen zu tun. Es scheint, dass die Verwendung einiger dynamic_cast
s dieses Problem beheben könnte, indem die verschiedenen abgeleiteten Klassen durch die verschiedenen Ebenen geleitet werden und dennoch völlig anders agieren können.
dynamic_cast
Nach meinem Verständnis wird RTTI verwendet, daher habe ich mich gefragt, wie machbar es wäre, es auf einem begrenzten System zu verwenden.
quelle
dynamic_cast
in C ++ zu verwenden, und jetzt, 9 von 10 Mal, wenn ich das Programm mit dem Debugger "kaputt mache", bricht es innerhalb der internen Dynamic-Cast-Funktion. Es ist verdammt langsam.Antworten:
Unabhängig vom Compiler können Sie jederzeit Laufzeit sparen, wenn Sie es sich leisten können
anstatt
Ersteres beinhaltet nur einen Vergleich von
std::type_info
; Letzteres beinhaltet notwendigerweise das Durchlaufen eines Vererbungsbaums plus Vergleiche.Darüber hinaus ... wie jeder sagt, ist die Ressourcennutzung implementierungsspezifisch.
Ich stimme den Kommentaren aller anderen zu, dass der Einreicher RTTI aus Designgründen vermeiden sollte. Es gibt jedoch gute Gründe, RTTI zu verwenden (hauptsächlich wegen boost :: any). Vor diesem Hintergrund ist es hilfreich, die tatsächliche Ressourcennutzung in gängigen Implementierungen zu kennen.
Ich habe kürzlich eine Reihe von Untersuchungen zu RTTI in GCC durchgeführt.
tl; dr: RTTI in GCC verwendet vernachlässigbaren Speicherplatz und
typeid(a) == typeid(b)
ist auf vielen Plattformen (Linux, BSD und möglicherweise eingebettete Plattformen, aber nicht mingw32) sehr schnell. Wenn Sie wissen, dass Sie immer auf einer gesegneten Plattform sind, ist RTTI nahezu kostenlos.Grobe Details:
GCC bevorzugt die Verwendung eines bestimmten "herstellerneutralen" C ++ - ABI [1] und verwendet diesen ABI immer für Linux- und BSD-Ziele [2]. Gibt für Plattformen, die diesen ABI und auch eine schwache Verknüpfung unterstützen,
typeid()
ein konsistentes und eindeutiges Objekt für jeden Typ zurück, auch über dynamische Verknüpfungsgrenzen hinweg. Sie können testen&typeid(a) == &typeid(b)
oder sich einfach darauf verlassen, dass der tragbare Testtypeid(a) == typeid(b)
tatsächlich nur einen Zeiger intern vergleicht.In GCCs bevorzugter ABI enthält eine Klasse vtable immer einen Zeiger auf eine RTTI-Struktur pro Typ, obwohl sie möglicherweise nicht verwendet wird. Ein
typeid()
Aufruf selbst sollte also nur so viel kosten wie jede andere vtable-Suche (genau wie das Aufrufen einer virtuellen Member-Funktion), und die RTTI-Unterstützung sollte keinen zusätzlichen Speicherplatz für jedes Objekt verwenden.Soweit ich das beurteilen kann, enthalten die von GCC verwendeten RTTI-Strukturen (dies sind alle Unterklassen von
std::type_info
) neben dem Namen nur wenige Bytes für jeden Typ. Mir ist nicht klar, ob die Namen im Ausgabecode auch mit vorhanden sind-fno-rtti
. In beiden Fällen sollte die Änderung der Größe der kompilierten Binärdatei die Änderung der Laufzeitspeichernutzung widerspiegeln.Ein kurzes Experiment (unter Verwendung von GCC 4.4.3 unter Ubuntu 10.04 64-Bit) zeigt, dass die Binärgröße eines einfachen Testprogramms
-fno-rtti
tatsächlich um einige hundert Bytes erhöht wird . Dies geschieht konsistent über Kombinationen von-g
und-O3
. Ich bin mir nicht sicher, warum die Größe zunehmen würde; Eine Möglichkeit besteht darin, dass sich der STL-Code von GCC ohne RTTI anders verhält (da Ausnahmen nicht funktionieren).[1] Bekannt als Itanium C ++ ABI, dokumentiert unter http://www.codesourcery.com/public/cxx-abi/abi.html . Die Namen sind schrecklich verwirrend: Der Name bezieht sich auf die ursprüngliche Entwicklungsarchitektur, obwohl die ABI-Spezifikation auf vielen Architekturen einschließlich i686 / x86_64 funktioniert. Kommentare in der internen Quelle und im STL-Code von GCC beziehen sich auf Itanium als "neuen" ABI im Gegensatz zu dem "alten", den sie zuvor verwendet haben. Schlimmer noch, der "neue" / Itanium ABI bezieht sich auf alle Versionen, die über erhältlich sind
-fabi-version
. Das "alte" ABI war älter als diese Versionierung. GCC hat das Itanium / versioned / "new" ABI in Version 3.0 übernommen. Das "alte" ABI wurde in Version 2.95 und früher verwendet, wenn ich ihre Änderungsprotokolle richtig lese.[2] Ich konnte keine Ressourcen finden, die die
std::type_info
Objektstabilität nach Plattform auflisten . Für Compiler, auf die ich Zugriff hatte, habe ich Folgendes verwendet :echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES
. Dieses Makro steuert das Verhalten vonoperator==
forstd::type_info
in der STL von GCC ab GCC 3.0. Ich habe festgestellt, dass mingw32-gcc dem Windows C ++ - ABI folgt, bei demstd::type_info
Objekte für einen Typ in DLLs nicht eindeutig sind.typeid(a) == typeid(b)
Anrufestrcmp
unter der Decke. Ich spekuliere, dass auf eingebetteten Einzelprogrammzielen wie AVR, bei denen es keinen Code zum Verknüpfen gibt,std::type_info
Objekte immer stabil sind.quelle
int
darfst eine werfen und es gibt keine vtable darin :))the latter necessarily involves traversing an inheritance tree plus comparisons
. @CoryB Sie können es sich "leisten", wenn Sie das Casting aus dem gesamten Vererbungsbaum nicht unterstützen müssen. Wenn Sie beispielsweise alle Elemente vom Typ X in einer Sammlung suchen möchten, jedoch nicht diejenigen, die von X abgeleitet sind, sollten Sie das erstere verwenden. Wenn Sie auch alle abgeleiteten Instanzen finden müssen, müssen Sie letztere verwenden.Vielleicht würden diese Zahlen helfen.
Ich habe einen kurzen Test damit gemacht:
5 Fälle wurden getestet:
5 ist nur mein eigentlicher Code, da ich ein Objekt dieses Typs erstellen musste, bevor ich prüfen konnte, ob es einem bereits vorhandenen ähnlich ist.
Ohne Optimierung
Für die die Ergebnisse waren (ich habe ein paar Läufe gemittelt):
Die Schlussfolgerung wäre also:
typeid()
ist mehr als doppelt so schnell wiedyncamic_cast
.Mit Optimierung (-Os)
Die Schlussfolgerung wäre also:
typeid()
ist fast x20 schneller alsdyncamic_cast
.Diagramm
Der Code
Wie in den Kommentaren angefordert, ist der Code unten (etwas chaotisch, funktioniert aber). ‚FastDelegate.h‘ ist erhältlich hier .
quelle
class a {}; class b : public a {}; class c : public b {};
wenn das Ziel eine Instanz vonc
ist, funktioniert es gut, wenn Sie auf Klasseb
mit testendynamic_cast
, aber nicht mit dertypeid
Lösung. Immer noch vernünftig, +1Es kommt auf das Ausmaß der Dinge an. Zum größten Teil sind es nur ein paar Überprüfungen und ein paar Zeiger-Dereferenzen. In den meisten Implementierungen befindet sich oben auf jedem Objekt mit virtuellen Funktionen ein Zeiger auf eine vtable, die eine Liste von Zeigern auf alle Implementierungen der virtuellen Funktion in dieser Klasse enthält. Ich würde vermuten, dass die meisten Implementierungen dies verwenden würden, um entweder einen anderen Zeiger auf die type_info-Struktur für die Klasse zu speichern.
Zum Beispiel in Pseudo-C ++:
Im Allgemeinen ist das eigentliche Argument gegen RTTI die Unwartbarkeit, dass Code jedes Mal geändert werden muss, wenn Sie eine neue abgeleitete Klasse hinzufügen. Berücksichtigen Sie diese Anweisungen nicht überall, sondern in virtuelle Funktionen. Dadurch wird der gesamte Code, der zwischen den Klassen unterschiedlich ist, in die Klassen selbst verschoben, sodass bei einer neuen Ableitung nur alle virtuellen Funktionen überschrieben werden müssen, um eine voll funktionsfähige Klasse zu werden. Wenn Sie jemals jedes Mal, wenn jemand den Typ einer Klasse überprüft und etwas anderes tut, eine große Codebasis durchsuchen mussten, werden Sie schnell lernen, sich von diesem Programmierstil fernzuhalten.
Wenn Sie mit Ihrem Compiler RTTI vollständig deaktivieren können, kann die endgültige Einsparung an Codegröße bei einem so kleinen RAM-Speicher erheblich sein. Der Compiler muss für jede einzelne Klasse mit einer virtuellen Funktion eine type_info-Struktur generieren. Wenn Sie RTTI deaktivieren, müssen alle diese Strukturen nicht im ausführbaren Image enthalten sein.
quelle
Nun, der Profiler lügt nie.
Da ich eine ziemlich stabile Hierarchie von 18 bis 20 Typen habe, die sich nicht sehr ändert, habe ich mich gefragt, ob die Verwendung eines einfachen enum'd-Mitglieds den Trick machen und die angeblich "hohen" Kosten von RTTI vermeiden würde. Ich war skeptisch, ob RTTI tatsächlich teurer war als nur die
if
darin eingeführte Aussage. Junge, Junge, ist es das?Es stellt sich heraus , dass RTTI ist teuer, viel teurer als eine gleichwertige
if
Anweisung oder einem einfachenswitch
auf eine primitive Variable in C ++. Die Antwort von S.Lott ist also nicht ganz richtig, es gibt zusätzliche Kosten für RTTI und es liegt nicht nur daran eineif
Aussage in der Mischung enthalten ist. Das liegt daran, dass RTTI sehr teuer ist.Dieser Test wurde auf dem Apple LLVM 5.0-Compiler mit aktivierten Bestandsoptimierungen durchgeführt (Standardeinstellungen für den Release-Modus).
Ich habe also unten 2 Funktionen, von denen jede den konkreten Typ eines Objekts entweder über 1) RTTI oder 2) einen einfachen Schalter ermittelt. Dies geschieht 50.000.000 Mal. Ohne weiteres präsentiere ich Ihnen die relativen Laufzeiten für 50.000.000 Läufe.
Das ist richtig, das
dynamicCasts
hat 94% der Laufzeit gekostet. Während derregularSwitch
Block nur 3,3% nahm .Lange Rede, kurzer Sinn: Wenn Sie sich die Energie leisten können, einen
enum
Typ wie unten einzuhängen, würde ich ihn wahrscheinlich empfehlen, wenn Sie RTTI durchführen müssen und die Leistung von größter Bedeutung ist. Das Mitglied muss nur einmal festgelegt werden (stellen Sie sicher, dass Sie es über alle Konstruktoren erhalten ), und schreiben Sie es anschließend nie mehr.Das heißt, dies sollte Ihre OOP-Praktiken nicht durcheinander bringen. Es soll nur verwendet werden, wenn Typinformationen einfach nicht verfügbar sind und Sie sich in der Verwendung von RTTI befinden.
quelle
Der Standardweg:
Standard-RTTI ist teuer, da ein zugrunde liegender Zeichenfolgenvergleich durchgeführt werden muss und daher die Geschwindigkeit von RTTI abhängig von der Länge des Klassennamens variieren kann.
Der Grund, warum Zeichenfolgenvergleiche verwendet werden, besteht darin, dass sie über Bibliotheks- / DLL-Grenzen hinweg konsistent funktionieren. Wenn Sie Ihre Anwendung statisch erstellen und / oder bestimmte Compiler verwenden, können Sie wahrscheinlich Folgendes verwenden:
Was garantiert nicht funktioniert (niemals ein falsches Positiv ergibt, aber möglicherweise ein falsches Negativ ergibt), aber bis zu 15-mal schneller sein kann. Dies hängt von der Implementierung von typeid () ab, um auf eine bestimmte Weise zu funktionieren, und Sie vergleichen lediglich einen internen Zeichenzeiger. Dies ist manchmal auch gleichbedeutend mit:
Sie können jedoch einen Hybrid sicher verwenden, der sehr schnell ist, wenn die Typen übereinstimmen, und der schlechteste Fall für nicht übereinstimmende Typen ist:
Um zu verstehen, ob Sie dies optimieren müssen, müssen Sie sehen, wie viel Zeit Sie damit verbringen, ein neues Paket zu erhalten, verglichen mit der Zeit, die für die Verarbeitung des Pakets benötigt wird. In den meisten Fällen ist ein Zeichenfolgenvergleich wahrscheinlich kein großer Aufwand. (abhängig von Ihrer Klasse oder Ihrem Namespace :: Klassennamenlänge)
Der sicherste Weg, dies zu optimieren, besteht darin, Ihre eigene Typ-ID als int (oder als Aufzählungstyp: int) als Teil Ihrer Basisklasse zu implementieren und damit den Typ der Klasse zu bestimmen. Verwenden Sie dann einfach static_cast <> oder reinterpret_cast < >
Für mich beträgt der Unterschied bei nicht optimiertem MS VS 2005 C ++ SP1 ungefähr das 15-fache.
quelle
typeid::operator
Arbeit . GCC auf einer unterstützten Plattform verwendet beispielsweise bereits Vergleiche vonchar *
s, ohne dass wir dies erzwingen - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/… . Sicher, Ihr Weg bewirkt, dass sich MSVC auf Ihrer Plattform viel besser verhält als der Standard, also ein großes Lob, und ich weiß nicht, was die "einige Ziele" sind, die Zeiger nativ verwenden ... aber mein Punkt ist, dass das Verhalten von MSVC in keiner Weise ist "Standard".Für eine einfache Überprüfung kann RTTI so billig sein wie ein Zeigervergleich. Für die Vererbungsprüfung kann es so teuer sein wie
strcmp
für jeden Typ in einem Vererbungsbaum, wenn Sie es sinddynamic_cast
in einer Implementierung von oben nach unten arbeiten.Sie können den Overhead auch reduzieren, indem Sie ihn nicht verwenden
dynamic_cast
indem Sie den Typ nicht explizit über & typeid (...) == & typeid (type) und stattdessen explizit überprüfen. Während dies nicht unbedingt für DLLs oder anderen dynamisch geladenen Code funktioniert, kann es für Dinge, die statisch verknüpft sind, recht schnell sein.Obwohl es zu diesem Zeitpunkt wie die Verwendung einer switch-Anweisung ist, können Sie loslegen.
quelle
Es ist immer am besten, Dinge zu messen. Im folgenden Code scheint unter g ++ die Verwendung der handcodierten Typidentifikation etwa dreimal schneller zu sein als bei RTTI. Ich bin sicher, dass eine realistischere handcodierte Implementierung mit Strings anstelle von Zeichen langsamer wäre und die Timings nahe beieinander liegen würde.
quelle
Vor einiger Zeit habe ich die Zeitkosten für RTTI in den speziellen Fällen von MSVC und GCC für einen 3-GHz-PowerPC gemessen. In den Tests, die ich ausgeführt habe (eine ziemlich große C ++ - App mit einem tiefen Klassenbaum),
dynamic_cast<>
kostete jeder zwischen 0,8 μs und 2 μs, je nachdem, ob er traf oder verfehlte.quelle
Das hängt ganz vom verwendeten Compiler ab. Ich verstehe, dass einige Zeichenfolgenvergleiche verwenden, andere echte Algorithmen.
Ihre einzige Hoffnung besteht darin, ein Beispielprogramm zu schreiben und zu sehen, was Ihr Compiler tut (oder zumindest zu bestimmen, wie viel Zeit es dauert, eine Million
dynamic_casts
oder eine Million auszuführentypeid
s ).quelle
RTTI kann billig sein und benötigt nicht unbedingt einen strcmp. Der Compiler begrenzt den Test, um die tatsächliche Hierarchie in umgekehrter Reihenfolge auszuführen. Wenn Sie also eine Klasse C haben, die ein Kind der Klasse B ist, das ein Kind der Klasse A ist, impliziert dynamic_cast von einem A * ptr zu einem C * ptr nur einen Zeigervergleich und nicht zwei (Übrigens ist nur der vptr-Tabellenzeiger verglichen). Der Test lautet wie folgt: "if (vptr_of_obj == vptr_of_C) return (C *) obj"
Ein weiteres Beispiel, wenn wir versuchen, dynamic_cast von A * nach B * zu übertragen. In diesem Fall überprüft der Compiler nacheinander beide Fälle (obj ist ein C und obj ist ein B). Dies kann auch auf einen einzelnen Test vereinfacht werden (meistens), da die virtuelle Funktionstabelle als Aggregation erstellt wird, sodass der Test mit "if (offset_of (vptr_of_obj, B) == vptr_of_B)" mit fortgesetzt wird
offset_of = return sizeof (vptr_table)> = sizeof (vptr_of_B)? vptr_of_new_methods_in_B: 0
Das Speicherlayout von
Woher weiß der Compiler, wie er dies zur Kompilierungszeit optimieren kann?
Zur Kompilierungszeit kennt der Compiler die aktuelle Hierarchie von Objekten, sodass er es ablehnt, eine andere Typhierarchie für dynamic_casting zu kompilieren. Dann muss es nur noch die Hierarchietiefe verarbeiten und die invertierte Anzahl von Tests hinzufügen, um dieser Tiefe zu entsprechen.
Dies wird beispielsweise nicht kompiliert:
quelle
RTTI kann "teuer" sein, da Sie jedes Mal, wenn Sie den RTTI-Vergleich durchführen, eine if-Anweisung hinzugefügt haben. In tief verschachtelten Iterationen kann dies teuer sein. In etwas, das niemals in einer Schleife ausgeführt wird, ist es im Wesentlichen kostenlos.
Sie können ein geeignetes polymorphes Design verwenden, um die if-Anweisung zu eliminieren. In tief verschachtelten Schleifen ist dies für die Leistung wesentlich. Ansonsten spielt es keine große Rolle.
RTTI ist auch teuer, weil es die Unterklassenhierarchie verdecken kann (falls es überhaupt eine gibt). Es kann den Nebeneffekt haben, das "objektorientierte" aus der "objektorientierten Programmierung" zu entfernen.
quelle
if
Anweisung, die Sie einführen, wenn Sie die Informationen zum Laufzeittyp auf diese Weise überprüfen.