Warum sind die schnellen Integer-Typen schneller als die anderen Integer-Typen?
Lesezeit: 7 Minuten
RobertS unterstützt Monica Cellio
In ISO/IEC 9899:2018 (C18) heißt es unter 7.20.1.3:
7.20.1.3 Schnellste Ganzzahltypen mit minimaler Breite
1 Jeder der folgenden Typen bezeichnet einen ganzzahligen Typ, der normalerweise am schnellsten ist268) unter allen Integer-Typen zu operieren, die mindestens die angegebene Breite haben.
2 Der typedef-Name int_fastN_t bezeichnet den schnellsten vorzeichenbehafteten ganzzahligen Typ mit einer Breite von mindestens N. Der Typedef-Name uint_fastN_t bezeichnet den schnellsten vorzeichenlosen ganzzahligen Typ mit einer Breite von mindestens N.
Alle anderen Arten dieses Formulars sind optional.
268) Es ist nicht garantiert, dass der angegebene Typ für alle Zwecke am schnellsten ist; Wenn die Implementierung keinen klaren Grund hat, einen Typ einem anderen vorzuziehen, wählt sie einfach einen ganzzahligen Typ aus, der die Vorzeichen- und Breitenanforderungen erfüllt.
Es wird jedoch nicht angegeben, warum diese “schnellen” Integer-Typen schneller sind.
Warum sind diese schnellen Integer-Typen schneller als die anderen Integer-Typen?
Ich habe die Frage mit C++ getaggt, da die schnellen Integer-Typen auch in C++17 in der Header-Datei von vorhanden sind cstdint. Leider gibt es in ISO/IEC 14882:2017 (C++17) keinen solchen Abschnitt über ihre Erklärung; Ich hatte diesen Abschnitt anderweitig im Fragetext implementiert.
Hinweis: In C werden sie in der Header-Datei von deklariert stdint.h.
Der entscheidende Punkt hier ist, dass diese Integer-Typen keine separaten, magisch schnelleren Typen sind. Sie sind nur Aliase für den normalen vorhandenen Typ, der auf dieser Maschine für diese Operation am schnellsten ist.
– mtraceur
6. Januar 2020 um 7:28 Uhr
Der Compiler gibt CPU-Operations-Opcodes aus, um Speicherorte und Register bestimmter Größen zu laden, zu speichern, zu maskieren und zu modifizieren; das ist alles, was die CPU sieht. Das Betriebssystem hat damit überhaupt nichts zu tun. Es ist alles, was der Compiler tut, genau so, als ob Sie die gegebene Typedef selbst angegeben hätten. (ICH vermuten Ein Compiler darf sie intern anders verarbeiten – vielleicht effizienter, wenn das möglich ist – als eine Benutzer-Typedef, solange es keinen sichtbaren Unterschied im Verhalten gibt.)
– Peter – Setzen Sie Monica wieder ein
6. Januar 2020 um 14:20 Uhr
@RobertS-ReinstateMonica Um genau zu sein, sind diese “Aliase” nur typedef Aussagen. So typisch, erfolgt dies auf der Ebene der Standardbibliothek. Natürlich legt die C-Norm keine wirkliche Einschränkung fest, was sie tun typedef zu – so ist zum Beispiel eine typische Implementierung vorzunehmen int_fast32_t a typedef von int auf einem 32-Bit-System, sondern einem hypothetischen Compiler könnte zum Beispiel implementieren a __int_fast intrinsischen Typ und versprechen, einige ausgefallene Optimierungen vorzunehmen, um von Fall zu Fall den schnellsten Maschinentyp für Variablen dieses Typs auszuwählen, und dann könnte die Bibliothek einfach typedef dazu.
– mtraceur
6. Januar 2020 um 21:53 Uhr
@RobertS-ReinstateMonica Ja, stimmt. Sie erhalten Programme mit maximaler Leistung mit architekturspezifischen Kompilierungs-Flags, die die Binärdatei weniger portabel machen.
– Peter – Setzen Sie Monica wieder ein
12. Januar 2020 um 19:01 Uhr
@RobertS-ReinstateMonica Es wird auf der Plattform, auf der es kompiliert wurde, am effizientesten sein zumnicht unbedingt an.
– HABO
17. Januar 2020 um 20:20 Uhr
Erorika
Stellen Sie sich eine CPU vor, die nur 64-Bit-Arithmetikoperationen ausführt. Stellen Sie sich nun vor, wie Sie eine vorzeichenlose 8-Bit-Addition auf einer solchen CPU implementieren würden. Es würde notwendigerweise mehr als eine Operation erfordern, um das richtige Ergebnis zu erzielen. Auf einer solchen CPU sind 64-Bit-Operationen schneller als Operationen auf anderen ganzzahligen Breiten. In dieser Situation alle Xint_fastY_t könnte vermutlich ein Alias vom Typ 64 Bit sein.
Wenn eine CPU schnelle Operationen für schmale Integer-Typen unterstützt und somit ein breiterer Typ nicht schneller ist als ein schmalerer, dann Xint_fastY_t wird (sollte) kein Alias des breiteren Typs sein, als notwendig ist, um alle Y-Bits darzustellen.
Aus Neugier habe ich die Größen auf einer bestimmten Implementierung (GNU, Linux) auf einigen Architekturen überprüft. Diese sind nicht bei allen Implementierungen auf derselben Architektur gleich:
Beachten Sie, dass, obwohl Operationen auf den größeren Typen schneller sein können, solche Typen auch mehr Platz im Cache beanspruchen und ihre Verwendung daher nicht unbedingt zu einer besseren Leistung führt. Außerdem kann man nicht immer darauf vertrauen, dass die Implementierung überhaupt die richtige Wahl getroffen hat. Wie immer ist das Messen für optimale Ergebnisse erforderlich.
Screenshot der Tabelle für Android-Benutzer:
(Android hat keine Box-Zeichnungszeichen in der Mono-Schriftart – ref)
Kommentare sind nicht für längere Diskussionen gedacht; diese Konversation wurde in den Chat verschoben.
– Samuel Liew ♦
6. Januar 2020 um 0:46 Uhr
Auf x86-64 ist die 32-Bit-Operandengröße für alles mindestens so schnell wie 64-Bit und spart Codegröße. Alle 32-Bit-Operationen erweitern das Ergebnis implizit auf 64 Bit. Glibcs Wahl von uint_fast32_t = uint64_t war eine schlechte Wahl für x86-64; Die 64-Bit-Integer-Division ist viel langsamer als 32-Bit auf Intel-CPUs, die 64-Bit-Multiplikation (und popcnt) ist auf einigen älteren und stromsparenden CPUs langsamer. Noch wichtiger ist, dass Sie in einem Array die doppelte Speicherbandbreite verschwenden und doppelt so viele SIMD-Elemente pro Vektor verlieren.
– Peter Cordes
23. Juli 2020 um 9:02 Uhr
IIRC, MUSL (eine alternative libc) verwendet uint_fast32_t = uint32_t auf x86-64. Der einzige Vorteil von 64-Bit-Integern auf x86-64 besteht darin, dass Sie manchmal vermeiden können, sie auf 64-Bit zu erweitern, wenn Sie sie als Array-Index verwenden.
– Peter Cordes
23. Juli 2020 um 9:02 Uhr
Der grundlegendere Punkt ist also der Es gibt keine einheitliche Antwort auf den schnellsten Typ für eine Größe, wie in den Kommentaren zu Warum wird uint32_t gegenüber uint_fast32_t bevorzugt? Manchmal ein int64_t Der lokale temporäre Schleifenzähler ist auf x86-64 optimal, abhängig vom umgebenden Code, während int32_t ist für ein Array oft schneller. (Ich denke immer noch, machen fast32 Typen 64-Bit auf x86-64 GNU war jedoch dumm)
– Peter Cordes
23. Juli 2020 um 9:07 Uhr
@PeterCordes: Es wäre hilfreich gewesen, wenn C getrennte, präzise umhüllende signierte Typen und unsignierte Typen mit lockerer Größe erkannt und die Möglichkeit zugelassen hätte, dass z. B. ein Wert größer als 65535 in a gespeichert wird uint_loose16_t dessen Adresse nicht verwendet wird, kann jeder Lesevorgang dieses Werts unabhängig den ursprünglichen Wert, einen auf 16 Bit gekürzten Wert oder einen auf eine Größe von mehr als 16 Bit gekürzten Wert ergeben. In Fällen, in denen eine solche Semantik akzeptabel wäre, wäre ein solcher Typ immer mindestens so schnell und oft schneller als irgendein vorzeichenloser ganzzahliger Typ unter den gegenwärtigen Regeln.
– Superkatze
22. März um 21:35 Uhr
Plugwash
Sie sind es nicht, zumindest nicht zuverlässig.
Die schnellen Typen sind einfach Typedefs für reguläre Typen, es liegt jedoch an der Implementierung, wie sie definiert werden. Sie müssen mindestens die gewünschte Größe haben, können aber auch größer sein.
Es ist wahr, dass auf einigen Architekturen einige Integer-Typen eine bessere Leistung aufweisen als andere. Zum Beispiel früh ARM Implementierungen hatten Speicherzugriffsanweisungen für 32-Bit-Wörter und für vorzeichenlose Bytes, aber sie hatten keine Anweisungen für Halbwörter oder vorzeichenbehaftete Bytes. Die Halbwort- und Signed-Byte-Befehle wurden später hinzugefügt, aber sie haben immer noch weniger flexible Adressierungsoptionen, weil sie in den freien Codierungsraum geschoben werden mussten. Darüber hinaus arbeiten alle aktuellen Datenverarbeitungsanweisungen auf ARM mit Wörtern, sodass es in einigen Fällen erforderlich sein kann, kleinere Werte nach der Berechnung zu maskieren, um korrekte Ergebnisse zu erhalten.
Es gibt jedoch auch die konkurrierende Sorge um den Cache-Druck, selbst wenn mehr Anweisungen erforderlich sind, um einen kleineren Wert zu laden/speichern/verarbeiten. Der kleinere Wert kann immer noch besser abschneiden, wenn er die Anzahl der Cache-Fehlschläge reduziert.
Die Definitionen der Typen auf vielen gängigen Plattformen scheinen nicht durchdacht zu sein. Insbesondere moderne 64-Bit-Plattformen neigen dazu, 32-Bit-Ganzzahlen gut zu unterstützen, aber die „schnellen“ Typen sind auf diesen Plattformen oft unnötigerweise 64-Bit.
Darüber hinaus werden Typen in C Teil der ABI der Plattform. Selbst wenn also ein Plattformanbieter feststellt, dass er dumme Entscheidungen getroffen hat, ist es schwierig, diese dummen Entscheidungen später zu ändern.
Ignorieren Sie die “schnellen” Typen. Wenn Sie sich wirklich Sorgen um die Integer-Leistung machen, vergleichen Sie Ihren Code mit allen verfügbaren Größen.
Die schnellen Typen sind nicht schneller als alle anderen Integer-Typen – sie sind es tatsächlich identisch zu einem “normalen” Integer-Typ (sie sind nur ein Alias für diesen Typ) — welcher Typ zufällig am schnellsten einen Wert von mindestens so vielen Bits hält.
Es ist nur plattformabhängig die Ganzzahltyp Jeder schnelle Typ ist ein Alias für.
14224600cookie-checkWarum sind die schnellen Integer-Typen schneller als die anderen Integer-Typen?yes
Der entscheidende Punkt hier ist, dass diese Integer-Typen keine separaten, magisch schnelleren Typen sind. Sie sind nur Aliase für den normalen vorhandenen Typ, der auf dieser Maschine für diese Operation am schnellsten ist.
– mtraceur
6. Januar 2020 um 7:28 Uhr
Der Compiler gibt CPU-Operations-Opcodes aus, um Speicherorte und Register bestimmter Größen zu laden, zu speichern, zu maskieren und zu modifizieren; das ist alles, was die CPU sieht. Das Betriebssystem hat damit überhaupt nichts zu tun. Es ist alles, was der Compiler tut, genau so, als ob Sie die gegebene Typedef selbst angegeben hätten. (ICH vermuten Ein Compiler darf sie intern anders verarbeiten – vielleicht effizienter, wenn das möglich ist – als eine Benutzer-Typedef, solange es keinen sichtbaren Unterschied im Verhalten gibt.)
– Peter – Setzen Sie Monica wieder ein
6. Januar 2020 um 14:20 Uhr
@RobertS-ReinstateMonica Um genau zu sein, sind diese “Aliase” nur
typedef
Aussagen. So typisch, erfolgt dies auf der Ebene der Standardbibliothek. Natürlich legt die C-Norm keine wirkliche Einschränkung fest, was sie tuntypedef
zu – so ist zum Beispiel eine typische Implementierung vorzunehmenint_fast32_t
atypedef
vonint
auf einem 32-Bit-System, sondern einem hypothetischen Compiler könnte zum Beispiel implementieren a__int_fast
intrinsischen Typ und versprechen, einige ausgefallene Optimierungen vorzunehmen, um von Fall zu Fall den schnellsten Maschinentyp für Variablen dieses Typs auszuwählen, und dann könnte die Bibliothek einfachtypedef
dazu.– mtraceur
6. Januar 2020 um 21:53 Uhr
@RobertS-ReinstateMonica Ja, stimmt. Sie erhalten Programme mit maximaler Leistung mit architekturspezifischen Kompilierungs-Flags, die die Binärdatei weniger portabel machen.
– Peter – Setzen Sie Monica wieder ein
12. Januar 2020 um 19:01 Uhr
@RobertS-ReinstateMonica Es wird auf der Plattform, auf der es kompiliert wurde, am effizientesten sein zumnicht unbedingt an.
– HABO
17. Januar 2020 um 20:20 Uhr