Ich bin mir des Leistungseinbruchs bewusst, wenn ich signierte Ints mit Floats mische.
Ist es schlimmer, nicht signierte Ints mit Floats zu mischen?
Gibt es einen Treffer beim Mischen von signierten / nicht signierten ohne Floats?
Haben die verschiedenen Größen (u32, u16, u8, i32, i16, i8) einen Einfluss auf die Leistung? Auf welchen Plattformen?
c++
performance
Luis
quelle
quelle
Antworten:
Der große Nachteil beim Mischen von Ints (jeglicher Art) und Floats besteht darin, dass sich diese in unterschiedlichen Registersätzen befinden. Um von einem Registersatz zum anderen zu gelangen, müssen Sie den Wert in den Speicher schreiben und zurücklesen, was zu einem Load-Hit-Store- Stall führt.
Wenn Sie zwischen verschiedenen Größen oder Signaturen von Ints wechseln, bleibt alles im selben Registersatz, sodass Sie die große Strafe vermeiden. Es kann kleinere Strafen aufgrund von Sign-Extensions usw. geben, aber diese sind viel kleiner als bei einem Load-Hit-Store.
quelle
Ich vermute, dass sich Informationen über die Xbox 360 und die PS3 wie die meisten Details auf niedriger Ebene hinter Wänden befinden, die nur für Entwickler lizenziert sind. Wir können jedoch ein äquivalentes x86-Programm erstellen und es zerlegen, um eine allgemeine Vorstellung zu erhalten.
Lassen Sie uns zunächst sehen, welche Kosten für die Erweiterung ohne Vorzeichen anfallen:
Der relevante Teil zerlegt sich in (unter Verwendung von GCC 4.4.5):
Also im Grunde das gleiche - in einem Fall bewegen wir ein Byte, in dem anderen bewegen wir ein Wort. Nächster:
Verwandelt sich in:
Die Kosten für die Vorzeichenerweiterung sind also unabhängig von den Kosten
movsbl
und nichtmovzbl
- der Unteranweisungsebene. Das ist auf modernen Prozessoren aufgrund der Arbeitsweise der modernen Prozessoren im Grunde unmöglich zu quantifizieren. Alles andere, angefangen von der Speichergeschwindigkeit über das Caching bis hin zu dem, was zuvor in der Pipeline war, wird die Laufzeit dominieren.In den ~ 10 Minuten, die ich für das Schreiben dieser Tests benötigt habe, hätte ich leicht einen echten Leistungsfehler finden können, und sobald ich eine Stufe der Compileroptimierung aktiviert habe, ist der Code für solch einfache Aufgaben nicht mehr erkennbar.
Dies ist kein Stapelüberlauf, daher hoffe ich, dass hier niemand behaupten wird, dass die Mikrooptimierung keine Rolle spielt. Spiele arbeiten oft mit sehr großen und numerischen Daten. Daher kann die sorgfältige Berücksichtigung von Verzweigungen, Besetzungen, Zeitplanung, Strukturanpassung usw. zu wichtigen Verbesserungen führen. Jeder, der viel Zeit mit der Optimierung von PPC-Code verbracht hat, hat wahrscheinlich mindestens eine Horrorgeschichte über Load-Hit-Stores. Aber in diesem Fall ist es wirklich egal. Die Speichergröße Ihres Integer-Typs wirkt sich nicht auf die Leistung aus, solange er ausgerichtet ist und in ein Register passt.
quelle
Signed Integer-Operationen können auf fast allen Architekturen teurer sein. Zum Beispiel ist die Division durch eine Konstante schneller, wenn sie nicht vorzeichenbehaftet ist, zB:
wird optimiert auf:
Aber...
optimiert auf:
oder auf Systemen, bei denen die Verzweigung billig ist,
Gleiches gilt für Modulo. Dies gilt auch für Nicht-Potenzen von 2 (das Beispiel ist jedoch komplexer). Wenn Ihre Architektur keine Hardwareteilung aufweist (z. B. die meisten ARMs), sind vorzeichenlose Teilungen von Nicht-Konstanten auch schneller.
Wenn Sie dem Compiler mitteilen, dass sich keine negativen Zahlen ergeben können, können Sie Ausdrücke optimieren, insbesondere solche, die für die Schleifenbeendigung und andere Bedingungen verwendet werden.
Was verschiedene Größen betrifft, gibt es zwar einen leichten Einfluss, aber Sie müssten diesen abwägen, anstatt weniger Speicher zu verschieben. In diesen Tagen gewinnen Sie wahrscheinlich mehr durch den Zugriff auf weniger Speicher als durch die Größenerweiterung. Sie sind an diesem Punkt sehr weit in der Mikrooptimierung.
quelle
Vorgänge mit signiertem oder nicht signiertem int verursachen auf aktuellen Prozessoren (x86_64, x86, PowerPC, Arm) dieselben Kosten. Auf 32-Bit-Prozessoren sollten u32, u16, u8, s32, s16, s8 identisch sein. Sie können mit schlechter Ausrichtung Strafe haben.
Die Konvertierung von int in float oder float in int ist jedoch eine kostspielige Operation. Sie können leicht eine optimierte Implementierung finden (SSE2, Neon ...).
Der wichtigste Punkt ist wahrscheinlich der Speicherzugriff. Wenn Ihre Daten nicht in den L1 / L2-Cache passen, verlieren Sie mehr Zyklus als Konvertierung.
quelle
Jon Purdy sagt oben (ich kann es nicht kommentieren), dass unsigned möglicherweise langsamer ist, weil es nicht überlaufen kann. Ich stimme nicht zu, vorzeichenlose Arithmetik ist einfache Moular-Arithmetik modulo 2 zur Anzahl der Bits im Wort. Vorzeichenbehaftete Operationen können im Prinzip Überläufe erleiden, sind jedoch normalerweise deaktiviert.
Manchmal können Sie clevere (aber nicht sehr lesbare) Dinge tun, z. B. zwei oder mehr Datenelemente in ein int packen und mehrere Operationen pro Anweisung erhalten (Taschenarithmetik). Aber du musst verstehen, was du tust. Natürlich können Sie dies mit MMX auf natürliche Weise tun. Wenn Sie jedoch manchmal die größte von HW unterstützte Wortgröße verwenden und die Daten manuell packen, erhalten Sie die schnellste Implementierung.
Seien Sie vorsichtig bei der Datenausrichtung. Bei den meisten HW-Implementierungen sind nicht ausgerichtete Lasten und Speicher langsamer. Natürliche Ausrichtung bedeutet, dass zum Beispiel ein 4-Byte-Wort ein Vielfaches von vier ist und acht-Byte-Wortadressen ein Vielfaches von acht Byte sein sollten. Dies überträgt sich auf SSE (128 Bit begünstigen die 16-Byte-Ausrichtung). AVX wird diese "Vektor" -Registergrößen bald auf 256 Bit und dann auf 512 Bit erweitern. Ausgerichtete Ladungen / Lager sind schneller als nicht ausgerichtete. Für HW-Geeks kann eine nicht ausgerichtete Speicheroperation Dinge wie Cacheline und sogar Seitengrenzen umfassen, bei denen die HW vorsichtig sein muss.
quelle
Es ist etwas besser, für Schleifenindizes vorzeichenbehaftete Ganzzahlen zu verwenden, da der vorzeichenbehaftete Überlauf in C undefiniert ist, sodass der Compiler davon ausgeht, dass solche Schleifen weniger Eckfälle haben. Dies wird durch gccs "-fstrict-overflow" (standardmäßig aktiviert) gesteuert und der Effekt ist wahrscheinlich schwer zu bemerken, ohne die Assembly-Ausgabe zu lesen.
Darüber hinaus funktioniert x86 besser, wenn Sie keine Typen mischen, da es Speicheroperanden verwenden kann. Wenn Typen konvertiert werden müssen (Vorzeichen- oder Null-Erweiterungen), bedeutet dies ein explizites Laden und die Verwendung eines Registers.
Halten Sie sich an int für lokale Variablen, und das meiste davon geschieht standardmäßig.
quelle
Celion weist darauf hin, dass der Mehraufwand beim Konvertieren zwischen Ints und Floats hauptsächlich mit dem Kopieren und Konvertieren von Werten zwischen Registern zusammenhängt. Der einzige Mehraufwand für unsignierte Ints an und für sich ergibt sich aus ihrem garantierten Wraparound-Verhalten, das eine gewisse Überlaufprüfung im kompilierten Code erfordert.
Die Konvertierung zwischen vorzeichenbehafteten und vorzeichenlosen Ganzzahlen verursacht im Grunde genommen keinen Mehraufwand. Verschiedene Größen von Integer kann (verschwindend) schneller oder langsamer Zugriff je nach Plattform. Im Allgemeinen ist die Größe der ganzen Zahl, die auf die Wortgröße der Plattform am nächsten ist die schnellste sein , Zugang, aber die Gesamtleistung Unterschied hängt von vielen anderen Faktoren ab , vor allem Größe Cache: Wenn Sie verwenden ,
uint64_t
wenn alles , was Sie brauchen , istuint32_t
, kann es Es kann sein, dass weniger Ihrer Daten sofort in den Cache passen und Sie möglicherweise einen gewissen Mehraufwand haben.Es ist jedoch etwas übertrieben, darüber nachzudenken. Wenn Sie Typen verwenden, die für Ihre Daten geeignet sind, sollten die Dinge einwandfrei funktionieren, und die Menge an Leistung, die durch die Auswahl von Typen auf der Grundlage der Architektur erzielt werden kann, ist ohnehin vernachlässigbar.
quelle