Gibt es einen Unterschied zwischen der Verwendung von a std::tuple
und nur Daten struct
?
typedef std::tuple<int, double, bool> foo_t;
struct bar_t {
int id;
double value;
bool dirty;
}
Nach dem, was ich online gefunden habe, habe ich festgestellt, dass es zwei Hauptunterschiede gibt: Der struct
ist besser lesbar, während der tuple
viele allgemeine Funktionen hat, die verwendet werden können. Sollte es einen signifikanten Leistungsunterschied geben? Ist das Datenlayout auch miteinander kompatibel (austauschbar gegossen)?
tuple
is-Implementierung ist definiert, daher hängt sie von Ihrer Implementierung ab. Persönlich würde ich nicht damit rechnen.Antworten:
Wir haben eine ähnliche Diskussion über Tupel und Struktur und ich schreibe einige einfache Benchmarks mit Hilfe eines meiner Kollegen, um die Unterschiede in der Leistungsdauer zwischen Tupel und Struktur zu identifizieren. Wir beginnen zunächst mit einer Standardstruktur und einem Tupel.
Wir verwenden dann Celero, um die Leistung unserer einfachen Struktur und unseres Tupels zu vergleichen. Nachfolgend finden Sie den Benchmark-Code und die Leistungsergebnisse, die mit gcc-4.9.2 und clang-4.0.0 erfasst wurden:
Mit clang-4.0.0 gesammelte Leistungsergebnisse
Und Leistungsergebnisse, die mit gcc-4.9.2 gesammelt wurden
Aus den obigen Ergebnissen können wir das klar erkennen
Tupel ist schneller als eine Standardstruktur
Binäre Produkte von clang haben eine höhere Leistung als gcc. clang-vs-gcc ist nicht der Zweck dieser Diskussion, daher werde ich nicht ins Detail gehen.
Wir alle wissen, dass das Schreiben eines == oder <oder> Operators für jede einzelne Strukturdefinition eine schmerzhafte und fehlerhafte Aufgabe sein wird. Ersetzen Sie unseren benutzerdefinierten Komparator durch std :: tie und führen Sie unseren Benchmark erneut aus.
Jetzt können wir sehen, dass die Verwendung von std :: tie unseren Code eleganter macht und es schwieriger ist, Fehler zu machen. Wir verlieren jedoch etwa 1% Leistung. Ich werde vorerst bei der std :: tie-Lösung bleiben, da ich auch eine Warnung zum Vergleichen von Gleitkommazahlen mit dem benutzerdefinierten Komparator erhalte.
Bis jetzt haben wir noch keine Lösung, um unseren Strukturcode schneller laufen zu lassen. Schauen wir uns die Swap-Funktion an und schreiben Sie sie neu, um zu sehen, ob wir eine Leistung erzielen können:
Mit clang-4.0.0 gesammelte Leistungsergebnisse
Und die mit gcc-4.9.2 gesammelten Leistungsergebnisse
Jetzt ist unsere Struktur etwas schneller als die eines Tupels (ungefähr 3% mit clang und weniger als 1% mit gcc). Wir müssen jedoch unsere angepasste Swap-Funktion für alle unsere Strukturen schreiben.
quelle
Wenn Sie mehrere verschiedene Tupel in Ihrem Code verwenden, können Sie die Anzahl der von Ihnen verwendeten Funktoren reduzieren. Ich sage das, weil ich oft die folgenden Arten von Funktoren verwendet habe:
Dies mag wie ein Overkill erscheinen, aber für jede Stelle innerhalb der Struktur müsste ich ein ganz neues Funktorobjekt mit einer Struktur erstellen, aber für ein Tupel ändere ich einfach
N
. Besser als das, ich kann dies für jedes einzelne Tupel tun, anstatt für jede Struktur und für jede Mitgliedsvariable einen ganz neuen Funktor zu erstellen. Wenn ich N Strukturen mit M Mitgliedsvariablen habe, die NxM-Funktoren sind, müsste ich sie erstellen (Worst-Case-Szenario), die auf ein kleines Stück Code komprimiert werden können.Wenn Sie sich für den Tupel-Weg entscheiden, müssen Sie natürlich auch Enums erstellen, um mit ihnen arbeiten zu können:
und boom, dein Code ist vollständig lesbar:
weil es sich selbst beschreibt, wenn Sie die darin enthaltenen Elemente erhalten möchten.
quelle
template <typename C, typename T, T C::*> struct struct_less { template <typename C> bool operator()(C const&, C const&) const; };
sollte also möglich sein. Es ist etwas weniger bequem, es zu buchstabieren, aber es wird nur einmal geschrieben.Tuple hat Standardkomparatoren eingebaut (für == und! = Vergleicht jedes Element, für <. <= ... vergleicht zuerst, wenn dasselbe das zweite vergleicht ...): http://en.cppreference.com/w/ cpp / Utility / Tupel / Operator_cmp
quelle
Nun, hier ist ein Benchmark, der keine Struktur von Tupeln innerhalb des struct-Operators == () erstellt. Es stellt sich heraus, dass die Verwendung von Tupel erhebliche Auswirkungen auf die Leistung hat, da zu erwarten ist, dass die Verwendung von PODs überhaupt keine Auswirkungen auf die Leistung hat. (Der Adressauflöser findet den Wert in der Befehlspipeline, bevor die Logikeinheit ihn überhaupt sieht.)
Häufige Ergebnisse, wenn dies auf meinem Computer mit VS2015CE unter Verwendung der Standardeinstellungen für "Release" ausgeführt wird:
Bitte Affe damit, bis Sie zufrieden sind.
quelle
-O3
,tuples
weniger Zeit in Anspruch nahm alsstructs
.Nun, eine POD-Struktur kann oft (ab) beim Lesen und Serialisieren von zusammenhängenden Chunks auf niedriger Ebene verwendet werden. Ein Tupel ist in bestimmten Situationen möglicherweise optimierter und unterstützt, wie Sie sagten, mehr Funktionen.
Verwenden Sie, was für die Situation besser geeignet ist, es gibt keine allgemeine Präferenz. Ich denke (aber ich habe es nicht bewertet), dass Leistungsunterschiede nicht signifikant sind. Das Datenlayout ist höchstwahrscheinlich nicht kompatibel und implementierungsspezifisch.
quelle
Was die "generische Funktion" betrifft, verdient Boost.Fusion etwas Liebe ... und insbesondere BOOST_FUSION_ADAPT_STRUCT .
Rippen von der Seite: ABRACADBRA
Dies bedeutet, dass alle Fusion-Algorithmen jetzt auf die Struktur anwendbar sind
demo::employee
.BEARBEITEN : In Bezug auf den Leistungsunterschied oder die Layoutkompatibilität ist
tuple
das Layout der Implementierung so definiert, dass es nicht kompatibel ist (und Sie sollten daher nicht zwischen den beiden Darstellungen wechseln), und im Allgemeinen würde ich dank des Leistungsunterschieds (zumindest in Release) keinen Unterschied erwarten Inlining vonget<N>
.quelle
tuple
s undstruct
s, nicht Boost!struct
insbesondere die Fülle von Algorithmen zur Manipulation von Tupeln gegen das Fehlen von Algorithmen zur Manipulation von Strukturen (beginnend mit der Iteration über ihre Felder). Diese Antwort zeigt, dass diese Lücke mithilfe von Boost.Fusion geschlossen werden kann, wodurchstruct
so viele Algorithmen wie auf Tupeln verfügbar sind. Ich habe einen kleinen Klappentext zu den genau zwei gestellten Fragen hinzugefügt.Seltsamerweise kann ich keine direkte Antwort auf diesen Teil der Frage sehen.
Die Antwort lautet: nein . Oder zumindest nicht zuverlässig, da das Layout des Tupels nicht spezifiziert ist.
Erstens ist Ihre Struktur ein Standardlayouttyp . Die Reihenfolge, Polsterung und Ausrichtung der Elemente wird durch eine Kombination aus Standard und Plattform-ABI genau definiert.
Wenn ein Tupel ein Standardlayouttyp wäre und wir wüssten, dass die Felder in der Reihenfolge angeordnet sind, in der die Typen angegeben sind, können wir sicher sein, dass sie mit der Struktur übereinstimmen.
Das Tupel wird normalerweise mithilfe der Vererbung auf zwei Arten implementiert: im alten rekursiven Loki / Modern C ++ Design-Stil oder im neueren variadischen Stil. Keiner ist ein Standardlayout-Typ, da beide die folgenden Bedingungen verletzen:
(vor C ++ 14)
(für C ++ 14 und höher)
da jede Blatt Basisklasse ein einzelnes Tupelelement enthält (NB. eine Einzelelement - Tupel wahrscheinlich ist ein Standard - Layout - Typ, wenn auch nicht sehr nützlichen). Wir wissen also, dass der Standard nicht garantiert, dass das Tupel die gleiche Auffüllung oder Ausrichtung wie die Struktur hat.
Darüber hinaus ist zu beachten, dass das ältere Tupel im rekursiven Stil die Datenelemente im Allgemeinen in umgekehrter Reihenfolge anordnet.
Anekdotisch hat es in der Vergangenheit bei einigen Compilern und Kombinationen von Feldtypen in der Praxis manchmal funktioniert (in einem Fall mit rekursiven Tupeln nach Umkehrung der Feldreihenfolge). Es funktioniert jetzt definitiv nicht zuverlässig (über Compiler, Versionen usw. hinweg) und wurde überhaupt nicht garantiert.
quelle
Es sollte keinen Leistungsunterschied geben (auch keinen unbedeutenden). Zumindest im Normalfall führen sie zum gleichen Speicherlayout. Trotzdem ist das Casting zwischen ihnen wahrscheinlich nicht erforderlich, um zu funktionieren (obwohl ich vermute, dass es eine ziemlich faire Chance gibt, dass dies normalerweise der Fall ist).
quelle
struct
muss jedem Unterobjekt mindestens 1 Byte zuweisen, während ich denke, dass atuple
mit der Optimierung der leeren Objekte davonkommen kann. Auch in Bezug auf Verpackung und Ausrichtung könnte es sein, dass Tupel mehr Spielraum haben.Ich habe die Erfahrung gemacht, dass sich mit der Zeit die Funktionalität von Typen (wie POD-Strukturen) einschleicht, die früher reine Dateninhaber waren. Dinge wie bestimmte Modifikationen, die keine Insiderkenntnisse der Daten, die Aufrechterhaltung von Invarianten usw. erfordern sollten.
Das ist eine gute Sache; Es ist die Grundlage der Objektorientierung. Dies ist der Grund, warum C mit Klassen erfunden wurde. Die Verwendung reiner Datensammlungen wie Tupel ist für eine solche logische Erweiterung nicht offen. Strukturen sind. Deshalb würde ich mich fast immer für Strukturen entscheiden.
Dies hängt damit zusammen, dass Tupel wie alle "offenen Datenobjekte" das Paradigma des Versteckens von Informationen verletzen. Sie können das später nicht ändern, ohne den Tupelgroßhandel wegzuwerfen. Mit einer Struktur können Sie schrittweise zu Zugriffsfunktionen übergehen.
Ein weiteres Problem ist die Typensicherheit und der selbstdokumentierende Code. Wenn Ihre Funktion ein Objekt vom Typ empfängt
inbound_telegram
oderlocation_3D
es klar ist; Wenn es ein erhältunsigned char *
odertuple<double, double, double>
nicht: Das Telegramm könnte ausgehend sein, und das Tupel könnte eine Übersetzung anstelle eines Ortes sein, oder vielleicht die Mindesttemperaturwerte vom langen Wochenende. Ja, Sie können tippen, um die Absichten klar zu machen, aber das hindert Sie nicht daran, Temperaturen zu überschreiten.Diese Probleme werden in der Regel bei Projekten wichtig, die eine bestimmte Größe überschreiten. Die Nachteile von Tupeln und die Vorteile aufwändiger Klassen werden nicht sichtbar und sind in kleinen Projekten tatsächlich ein Aufwand. Das Starten mit richtigen Klassen auch für unauffällige kleine Datenaggregate zahlt sich spät aus.
Natürlich wäre eine praktikable Strategie, einen reinen Datenhalter als zugrunde liegenden Datenanbieter für einen Klassen-Wrapper zu verwenden, der Operationen für diese Daten bereitstellt.
quelle
Sorgen Sie sich nicht um Geschwindigkeit oder Layout, das ist Nano-Optimierung und hängt vom Compiler ab, und es gibt nie genug Unterschiede, um Ihre Entscheidung zu beeinflussen.
Sie verwenden eine Struktur für Dinge, die sinnvoll zusammengehören, um ein Ganzes zu bilden.
Sie verwenden ein Tupel für Dinge, die zufällig zusammen sind. Sie können ein Tupel spontan in Ihrem Code verwenden.
quelle
Ich weiß, dass es ein altes Thema ist, aber ich bin jetzt dabei, eine Entscheidung über einen Teil meines Projekts zu treffen: Sollte ich den Tupel- oder Strukturweg gehen. Nachdem ich diesen Thread gelesen habe, habe ich einige Ideen.
Über die Wheaties und den Leistungstest: Bitte beachten Sie, dass Sie normalerweise memcpy, memset und ähnliche Tricks für Strukturen verwenden können. Dies würde die Leistung VIEL besser machen als bei Tupeln.
Ich sehe einige Vorteile in Tupeln:
Ich habe das Web durchsucht und schließlich diese Seite erreicht: https://arne-mertz.de/2017/03/smelly-pair-tuple/
Generell stimme ich einer abschließenden Schlussfolgerung von oben zu.
quelle