Es scheint, dass dies uint32_t
viel häufiger vorkommt als uint_fast32_t
(mir ist klar, dass dies ein anekdotischer Beweis ist). Das erscheint mir allerdings kontraintuitiv.
Fast immer, wenn ich sehe, dass eine Implementierung verwendet wird uint32_t
, ist alles, was sie wirklich will, eine Ganzzahl, die Werte bis zu 4.294.967.295 enthalten kann (normalerweise eine viel niedrigere Grenze irgendwo zwischen 65.535 und 4.294.967.295).
Es scheint seltsam zu sein, diese zu verwenden uint32_t
, da die Garantie "genau 32 Bit" nicht benötigt wird und die Garantie "schnellste verfügbare> = 32 Bit" von uint_fast32_t
genau die richtige Idee zu sein scheint. Darüber hinaus ist, obwohl es normalerweise implementiert uint32_t
ist, nicht garantiert, dass es existiert.
Warum wäre uint32_t
dann bevorzugt? Ist es einfach besser bekannt oder gibt es technische Vorteile gegenüber den anderen?
uint32_fast_t
, was, wenn ich es richtig verstehe, mindestens 32 Bit ist (was bedeutet, dass es mehr sein könnte? Klingt für mich irreführend). Ich verwende derzeituint32_t
und Freunde in meinem Projekt, weil ich diese Daten packe und über das Netzwerk sende. Ich möchte, dass Sender und Empfänger genau wissen, wie groß die Felder sind. Klingt so, als wäre dies möglicherweise nicht die robusteste Lösung, da eine Plattform möglicherweise nicht implementiert wirduint32_t
, aber alle von mir tun dies anscheinend, sodass ich mit dem, was ich tue, einverstanden bin.uint32_t
das gibt Ihnen das nicht (und es ist schade, dass es keinuint32_t_be
und gibtuint32_t_le
, was für fast jeden möglichen Fall besser geeignet wäre, in demuint32_t
derzeit die beste Option ist).Antworten:
uint32_t
wird garantiert fast die gleichen Eigenschaften auf jeder Plattform haben, die es unterstützt. 1uint_fast32_t
hat nur sehr wenige Garantien dafür, wie es sich auf verschiedenen Systemen im Vergleich verhält.Wenn Sie zu einer Plattform
uint_fast32_t
mit einer anderen Größe wechseln , muss der gesamte verwendete Codeuint_fast32_t
erneut getestet und validiert werden. Alle Stabilitätsannahmen werden aus dem Fenster sein. Das gesamte System wird anders funktionieren.Wenn Sie Ihren Code schreiben, haben Sie möglicherweise nicht einmal Zugriff auf ein
uint_fast32_t
System mit einer Größe von 32 Bit.uint32_t
funktioniert nicht anders (siehe Fußnote).Korrektheit ist wichtiger als Geschwindigkeit. Vorzeitige Korrektheit ist daher ein besserer Plan als vorzeitige Optimierung.
Für den Fall, dass ich Code für Systeme
uint_fast32_t
mit 64 oder mehr Bits schreibe , kann ich meinen Code für beide Fälle testen und verwenden. Abgesehen von Bedarf und Gelegenheit ist dies ein schlechter Plan.Schließlich ,
uint_fast32_t
wenn Sie es für längere Zeit oder Anzahl der Instanzen kann langsamer als sein werden speichertuint32
einfach Größe aufgrund von Cache - Probleme und Speicherbandbreite. Heutzutage sind Computer weitaus häufiger speichergebunden als CPU-gebunden unduint_fast32_t
können isoliert schneller sein, jedoch nicht, nachdem Sie den Speicheraufwand berücksichtigt haben.1 Wie @chux in einem Kommentar festgestellt hat, durchläuft die Arithmetik on , wenn sie
unsigned
größer als istuint32_t
,uint32_t
die üblichen ganzzahligen Beförderungen, und wenn nicht, bleibt sie unverändertuint32_t
. Dies kann zu Fehlern führen. Nichts ist jemals perfekt.quelle
unsigned
es breiter als istuint32_t
und dannuint32_t
auf einer Plattform die üblichen ganzzahligen Aktionen durchläuft und auf einer anderen nicht. Mituint32_t
diesen ganzzahligen mathematischen Problemen werden sie jedoch erheblich reduziert.uint32_t
ist dort, wo die genauen Details der Maschinendarstellung des Typs wichtig sind, währenduint_fast32_t
dort, wo die Rechengeschwindigkeit am wichtigsten ist, (Un-) Vorzeichen und Mindestreichweite wichtig sind und Details der Darstellung nicht wesentlich sind. Es gibt auchuint_least32_t
Fälle, in denen (Un-) Signiertheit und Mindestreichweite am wichtigsten sind, Kompaktheit wichtiger als Geschwindigkeit ist und eine genaue Darstellung nicht wesentlich ist.uint32_t
eher als die anderen Typen verwenden, ist, dass sie normalerweise keine solche Hardware haben, auf der sie testen können . (Gleiches giltint32_t
in geringerem Maße und sogarint
undshort
).unsigned short
==uint32_t
undint
==int48_t
. Wenn Sie so etwas wie berechnen(uint32_t)0xFFFFFFFF * (uint32_t)0xFFFFFFFF
, werden die Operanden hochgestuftsigned int
und lösen einen vorzeichenbehafteten Ganzzahlüberlauf aus, was ein undefiniertes Verhalten ist. Siehe diese Frage.Hinweis: Falsch benannt
uint32_fast_t
sollte seinuint_fast32_t
.uint32_t
hat eine strengere Spezifikation alsuint_fast32_t
und sorgt so für eine konsistentere Funktionalität.uint32_t
Profis:uint32_t
Nachteile:Beispiel: Plattformen ohne 8/16/32-Bit-Ganzzahlen ( 9/18 / 36- Bit, andere ).
Beispiel: Plattformen mit Nicht-2-Komplement. alte 2200
uint_fast32_t
Profis:Dies ermöglicht immer allen neuen und alten Plattformen, schnelle / minimale Typen zu verwenden.
uint_fast32_t
Nachteile:uint32_fast_t
. Sieht so aus, als ob viele diesen Typ einfach nicht brauchen und benutzen. Wir haben nicht einmal den richtigen Namen verwendet!uint_fast32_t
handelt sich also nur um eine Annäherung 1. Ordnung.Am Ende hängt das Beste vom Codierungsziel ab. Verwenden Sie diese Option, es sei denn, Sie codieren für eine sehr breite Portabilität oder eine Nischenleistungsfunktion
uint32_t
.Bei der Verwendung dieser Typen kommt ein weiteres Problem ins Spiel: ihr Rang im Vergleich zu
int/unsigned
Vermutlich
uint_fastN_t
könnte der Rang von seinunsigned
. Dies ist nicht spezifiziert, sondern eine bestimmte und überprüfbare Bedingung.Somit
uintN_t
ist es wahrscheinlicher alsuint_fastN_t
enger zu seinunsigned
. Dies bedeutet, dass Code, deruintN_t
Mathematik verwendet, mit größerer Wahrscheinlichkeit ganzzahligen Beförderungen unterliegt alsuint_fastN_t
in Bezug auf die Portabilität.Mit diesem Problem: Portabilitätsvorteil
uint_fastN_t
bei ausgewählten mathematischen Operationen.Randnotiz über
int32_t
anstattint_fast32_t
: Auf seltenen MaschinenINT_FAST32_MIN
kann -2.147.483.647 und nicht -2.147.483.648 sein. Der größere Punkt:(u)intN_t
Typen sind eng spezifiziert und führen zu portablem Code.quelle
uint32_fast_t
handelt es sich um einen 64-Bit-Typ, der die gelegentliche Vorzeichenerweiterung speichert undimul rax, [mem]
bei Verwendung mit 64-Bit-Ganzzahlen oder -Zeigern anstelle eines separaten Ladebefehls mit null Erweiterung ermöglicht . Aber das ist alles, was Sie für den Preis des doppelten Cache-Footprints und der zusätzlichen Codegröße (REX-Präfix auf allem) erhaltenpopcnt
. Und denken Sie daran, dass es nur sicher ist, diesen Typ für 32-Bit-Werte zu verwenden, da er auf anderen Architekturen kleiner ist, sodass Sie diese Kosten für nichts bezahlen.uint32_fast_t
eine schreckliche Wahl ist, auf x86 zu arbeiten. Die Operationen, die schneller sind, sindimul rax, [mem]
selten und der Vorteil, wenn sie auftreten, ist meist winzig : Die Unterschiede für den Fall, dass @PeterCordes erwähnt, sind sehr , sehr gering: ein einzelnes UOP in der fusionierten Domäne und Null in der nicht fusionierten Domäne. In den interessantesten Szenarien wird nicht einmal ein einziger Zyklus hinzugefügt. Wägen Sie ab, dass es gegen die doppelte Speichernutzung und eine schlechtere Vektorisierung schwer fällt, sehr oft zu gewinnen.fast_t
noch schlimmer machenint
: Es hat nicht nur unterschiedliche Größen auf unterschiedlichen Plattformen, sondern auch unterschiedliche Größen, abhängig von Optimierungsentscheidungen und unterschiedlichen Größen in unterschiedlichen Dateien! In der Praxis denke ich, dass es selbst bei der Optimierung des gesamten Programms nicht funktionieren kann: Die Größen in C und C ++ sind so festgelegt, dasssizeof(uint32_fast_t)
alles, was bestimmt, dass es sogar direkt immer den gleichen Wert zurückgeben muss, für den Compiler sehr schwierig wäre mache eine solche Transformation.Dumme Antwort:
uint32_fast_t
, die korrekte Schreibweise istuint_fast32_t
.Praktische Antwort:
uint32_t
oderint32_t
für ihre genaue Semantik genau 32 Bit mit vorzeichenlosem Wrap-Around-Arithmetik (uint32_t
) oder 2er-Komplementdarstellung (int32_t
). Diexxx_fast32_t
Typen können größer und daher ungeeignet sein, um sie in Binärdateien zu speichern, in gepackten Arrays und Strukturen zu verwenden oder über ein Netzwerk zu senden. Darüber hinaus sind sie möglicherweise nicht einmal schneller.Pragmatische Antwort:
uint_fast32_t
, wie in Kommentaren und Antworten gezeigt, und gehen wahrscheinlich davon ausunsigned int
, dass sie dieselbe Semantik haben, obwohl viele aktuelle Architekturen immer noch 16-Bit-int
Werte haben und einige seltene Museumsbeispiele andere seltsame int-Größen unter 32.UX-Antwort:
uint32_t
,uint_fast32_t
ist die Verwendung langsamer: Die Eingabe dauert länger, insbesondere wenn Rechtschreibung und Semantik in der C-Dokumentation nachgeschlagen werden ;-)Eleganz ist wichtig (offensichtlich meinungsbasiert):
uint32_t
sieht so schlecht aus, dass viele Programmierer es vorziehen, ihre eigenenu32
oderuint32
Typen zu definieren ... Aus dieser Perspektiveuint_fast32_t
sieht es irreparabel aus. Kein Wunder, dass es mit seinen Freundenuint_least32_t
und so auf der Bank sitzt .quelle
std::reference_wrapper
ich denke, aber manchmal frage ich mich, ob das Standardkomitee wirklich will, dass die Typen, die es standardisiert, verwendet werden ...Ein Grund dafür ist, dass
unsigned int
es bereits "am schnellsten" ist, ohne dass spezielle Typedefs erforderlich sind oder etwas hinzugefügt werden muss. Wenn Sie es also schnell brauchen, verwenden Sie einfach das Fundamentalint
oder denunsigned int
Typ.Der Standard garantiert zwar nicht ausdrücklich, dass er am schnellsten ist, tut dies jedoch indirekt , indem er in 3.9.1 angibt, dass "einfache Ints die natürliche Größe haben, die von der Architektur der Ausführungsumgebung vorgeschlagen wird" . Mit anderen Worten,
int
(oder sein nicht signiertes Gegenstück) ist das, womit sich der Prozessor am wohlsten fühlt.Jetzt wissen Sie natürlich nicht, wie groß das sein
unsigned int
könnte. Sie wissen nur, dass es mindestens so groß ist wieshort
(und ich scheine mich zu erinnern, dassshort
es mindestens 16 Bit sein muss, obwohl ich das jetzt im Standard nicht finden kann!). Normalerweise sind es einfach nur 4 Bytes, aber theoretisch könnte es größer oder im Extremfall sogar kleiner sein (obwohl ich persönlich noch nie auf eine Architektur gestoßen bin, in der dies der Fall war, nicht einmal auf 8-Bit-Computern in den 1980er Jahren. .. vielleicht waren einige Mikrocontroller, die wissen, dass ich an Demenz leide,int
damals ganz klar 16 Bit).Der C ++ - Standard gibt nicht an, welche
<cstdint>
Typen es gibt oder was sie garantieren, sondern erwähnt lediglich "wie in C".uint32_t
garantiert gemäß dem C-Standard, dass Sie genau 32 Bit erhalten. Nichts anderes, nichts weniger und keine Füllbits. Manchmal ist dies genau das, was Sie brauchen, und daher ist es sehr wertvoll.uint_least32_t
garantiert, dass es unabhängig von der Größe nicht kleiner als 32 Bit sein kann (aber es könnte sehr gut größer sein). Manchmal, aber viel seltener als ein genaues Witz oder "egal", ist dies das, was Sie wollen.Schließlich
uint_fast32_t
ist meiner Meinung nach etwas überflüssig, außer zu Zwecken der Absichtserklärung. Der C-Standard besagt "bezeichnet einen ganzzahligen Typ, der normalerweise am schnellsten ist" (beachten Sie das Wort "normalerweise") und erwähnt ausdrücklich, dass er nicht für alle Zwecke am schnellsten sein muss. Mit anderen Worten,uint_fast32_t
ist ungefähr das Gleiche wieuint_least32_t
, was normalerweise auch am schnellsten ist, nur keine Garantie gegeben (aber keine Garantie so oder so).Da Sie sich die meiste Zeit entweder nicht um die genaue Größe kümmern oder genau 32 (oder 64, manchmal 16) Bits möchten und der
unsigned int
Typ "egal" sowieso am schnellsten ist, erklärt dies, warum dies nicht der Falluint_fast32_t
ist häufig benutzt.quelle
int
auf 8-Bit-Prozessoren nicht an 16-Bit erinnern. Ich kann mich an keine aus jenen Tagen erinnern, in denen etwas Größeres verwendet wurde. Wenn Speicher zur Verfügung steht, verwendeten Compiler für segmentierte x86-Architektur ebenfalls 16-Bitint
.int
32 Bit waren (was ich mir als Beispiel vorgestellt habe). Es war nicht ...int
sollte in der Vergangenheit der schnellste Typ mit einer minimalen Breite von 16 Bit sein (aus diesem Grund hat C eine ganzzahlige Heraufstufungsregel), aber heute ist dies bei 64-Bit-Architekturen nicht mehr der Fall. Zum Beispiel sind 8-Byte-Ganzzahlen schneller als 4-Byte-Ganzzahlen auf x86_64-Bit, da der Compiler bei 4-Byte-Ganzzahlen einen zusätzlichen Befehl einfügen muss, der den 4-Byte-Wert in einen 8-Byte-Wert erweitert, bevor er ihn mit anderen 8-Byte-Werten vergleicht.long
aus historischen Gründen 32-Bit sein muss undint
jetzt nicht breiter als sein musslong
, sodassint
32-Bit möglicherweise beibehalten werden muss, selbst wenn 64 Bit schneller wären.Ich habe keine Beweise gesehen,
uint32_t
die für seine Reichweite verwendet werden könnten. Stattdessen wird die meiste Zeit, die ich gesehen habeuint32_t
, genau 4 Oktette Daten in verschiedenen Algorithmen gespeichert, mit garantierter Umlauf- und Verschiebungssemantik!Es gibt auch andere Gründe für die Verwendung
uint32_t
anstelle vonuint_fast32_t
: Oft ist es so, dass es einen stabilen ABI liefert. Zusätzlich kann die Speichernutzung genau bekannt sein. Dies gleicht sehr viel aus, unabhängig davon, von welchem Geschwindigkeitsgewinn er stammen würdeuint_fast32_t
, wenn sich dieser Typ von dem von unterscheidetuint32_t
.Für Werte <65536 gibt es bereits einen praktischen Typ, der aufgerufen wird
unsigned int
(unsigned short
muss mindestens diesen Bereich haben, hat aberunsigned int
die native Wortgröße). Für Werte <4294967296 gibt es einen anderen aufgerufenen Typunsigned long
.Und schließlich verwenden die Leute nicht,
uint_fast32_t
weil es ärgerlich lang zu tippen und leicht zu tippen ist: D.quelle
short
Bearbeitung geändert .int
ist vermutlich das schnelle , wenn es sich von unterscheidetshort
.unsigned int
anstelle von verwenden solltenuint16_fast_t
, behaupten Sie, Sie wissen es besser als der Compiler.unsigned long
ist keine gute Wahl, wenn Ihre Plattform 64-Bit-long
s hat und Sie nur Zahlen benötigen<2^32
.uint16_t
als auch überlegenuint_fast16_t
. Wennuint_fast16_t
sie lockerer als normale Ganzzahltypen angegeben würden, sodass ihr Bereich für Objekte, deren Adressen nicht verwendet werden, nicht konsistent sein muss, könnte dies einige Leistungsvorteile auf Plattformen bieten, die intern 32-Bit-Arithmetik ausführen, aber über einen 16-Bit-Datenbus verfügen . Der Standard erlaubt jedoch keine solche Flexibilität.Mehrere Gründe.
Zusammenfassend sind die "schnellen" Typen wertloser Müll. Wenn Sie wirklich herausfinden müssen, welcher Typ für eine bestimmte Anwendung am schnellsten ist, müssen Sie Ihren Code auf Ihrem Compiler vergleichen.
quelle
Unter dem Gesichtspunkt der Korrektheit und der einfachen Codierung
uint32_t
hat dies viele Vorteile,uint_fast32_t
insbesondere aufgrund der genau definierten Größe und arithmetischen Semantik, wie viele Benutzer oben hervorgehoben haben.Was vielleicht wurde verpasst ist , dass der eine vermeintliche Vorteil von
uint_fast32_t
- , dass es sein kann , schneller , einfach nie in sinnvoller Weise verwirklicht. Die meisten 64-Bit-Prozessoren, die die 64-Bit-Ära dominiert haben (x86-64 und Aarch64 meistens), sind aus 32-Bit-Architekturen hervorgegangen und verfügen selbst im 64-Bit-Modus über schnelle native 32-Bit-Operationen. Souint_fast32_t
ist es auchuint32_t
auf diesen Plattformen.Selbst wenn einige der "ebenfalls ausgeführten" Plattformen wie POWER, MIPS64 und SPARC nur 64-Bit-ALU-Operationen anbieten, kann die überwiegende Mehrheit der interessanten 32-Bit-Operationen problemlos auf 64-Bit-Registern ausgeführt werden: die unteren 32-Bit-Operationen die gewünschten Ergebnisse erzielen (und auf allen gängigen Plattformen können Sie mindestens 32-Bit laden / speichern). Die Linksverschiebung ist das Hauptproblem, aber selbst das kann in vielen Fällen durch Optimierungen der Wert- / Bereichsverfolgung im Compiler optimiert werden.
Ich bezweifle, dass die gelegentlich etwas langsamere Linksverschiebung oder die 32x32 -> 64-Multiplikation die doppelte Speichernutzung für solche Werte in allen außer den dunkelsten Anwendungen überwiegen wird.
Abschließend möchte ich darauf hinweisen, dass der Kompromiss zwar weitgehend als "Speichernutzung und Vektorisierungspotential" (zugunsten
uint32_t
) gegenüber der Befehlsanzahl / Geschwindigkeit (zugunstenuint_fast32_t
) charakterisiert wurde - auch das ist mir nicht klar. Ja, auf einigen Plattformen benötigen Sie zusätzliche Anweisungen für einige 32-Bit-Vorgänge, aber Sie speichern auch einige Anweisungen, weil:struct two32{ uint32_t a, b; }
inrax
like zu einer einzigentwo32{1, 2}
optimiert werden,mov rax, 0x20001
während die 64-Bit-Version zwei Anweisungen benötigt. Im Prinzip sollte dies auch für benachbarte arithmetische Operationen (gleiche Operation, unterschiedlicher Operand) möglich sein, aber ich habe es in der Praxis nicht gesehen.Kleinere Datentypen nutzen häufig bessere moderne Anrufkonventionen wie das SysV ABI, mit denen Datenstrukturdaten effizient in Register gepackt werden. Beispielsweise können Sie in Registern bis zu einer 16-Byte-Struktur zurückgeben
rdx:rax
. Für eine Funktion, die eine Struktur mit 4uint32_t
Werten zurückgibt (initialisiert von einer Konstanten), wird dies übersetzt inret_constant32(): movabs rax, 8589934593 movabs rdx, 17179869187 ret
Dieselbe Struktur mit 4 64-Bit
uint_fast32_t
benötigt eine Registerverschiebung und vier Speicher im Speicher, um dasselbe zu tun (und der Aufrufer muss wahrscheinlich die Werte nach der Rückkehr aus dem Speicher zurücklesen):ret_constant64(): mov rax, rdi mov QWORD PTR [rdi], 1 mov QWORD PTR [rdi+8], 2 mov QWORD PTR [rdi+16], 3 mov QWORD PTR [rdi+24], 4 ret
In ähnlicher Weise werden beim Übergeben von Strukturargumenten 32-Bit-Werte etwa doppelt so dicht in die für Parameter verfügbaren Register gepackt, sodass es weniger wahrscheinlich ist, dass Ihnen die Registerargumente ausgehen und Sie auf den Stapel 1 gelangen müssen .
Selbst wenn Sie sich
uint_fast32_t
für Orte entscheiden, an denen "Geschwindigkeit wichtig ist", haben Sie häufig auch Orte, an denen Sie einen Typ mit fester Größe benötigen. Zum Beispiel, wenn Sie Werte für externe Ausgaben von externen Eingaben als Teil Ihres ABI, als Teil einer Struktur, die ein bestimmtes Layout benötigt, oder weil Sie intelligentuint32_t
für große Aggregationen von Werten verwenden, um Speicherplatz zu sparen. An den Orten , wo Ihruint_fast32_t
und `` uint32_t` Typen Schnittstelle benötigen, können Sie (zusätzlich zu der Entwicklung Komplexität), unnötige Zeichen Erweiterungen oder andere Größe-Mismatch - bezogenen Code finden. Compiler leisten in vielen Fällen gute Arbeit, um dies zu optimieren, aber es ist immer noch nicht ungewöhnlich, dass dies beim Mischen von Typen unterschiedlicher Größe in einer optimierten Ausgabe angezeigt wird.Sie können mit einigen der obigen Beispiele und mehr auf Godbolt spielen .
1 Um klar zu sein, ist die Konvention, Strukturen dicht in Register zu packen, nicht immer ein klarer Gewinn für kleinere Werte. Dies bedeutet jedoch, dass die kleineren Werte möglicherweise "extrahiert" werden müssen, bevor sie verwendet werden können. Zum Beispiel benötigt eine einfache Funktion, die die Summe der beiden Strukturelemente zusammen zurückgibt, eine
mov rax, rdi; shr rax, 32; add edi, eax
Weile. Für die 64-Bit-Version erhält jedes Argument ein eigenes Register und benötigt nur ein einzelnesadd
oderlea
. Wenn Sie jedoch akzeptieren, dass das Design "Strukturen beim Übergeben dicht packen" insgesamt sinnvoll ist, werden kleinere Werte diese Funktion besser nutzen.quelle
uint_fast32_t
, was IMO ein Fehler ist. (Anscheinend ist Windows unter Windowsuint_fast32_t
ein 32-Bit-Typ.) 64-Bit unter x86-64 Linux zu sein, ist der Grund, warum ich niemandem empfehlen würde, es zu verwendenuint_fast32_t
: Es ist für eine niedrige Befehlsanzahl optimiert (Funktionsargumente und Rückgabewerte benötigen für nie eine Erweiterung von Null Verwendung als Array-Index) nicht für die Gesamtgeschwindigkeit oder Codegröße auf einer der wichtigsten wichtigen Plattformen.pair<int,bool>
oder zurückgegeben wirdpair<int,int>
. Wenn beide Mitglieder keine Konstanten zur Kompilierungszeit sind, gibt es normalerweise mehr als nur ein ODER, und der Aufrufer muss die Rückgabewerte entpacken. ( bugs.llvm.org/show_bug.cgi?id=34840 LLVM optimiert die Rückgabe von Rückgabewerten für private Funktionen und sollte 32-Bit-Int als Ganzes behandeln,rax
sodass dasbool
getrennt ist,dl
anstatt eine 64-Bit-Konstante zu benötigentest
it.)__attribute__((noinline))
genau, um sicherzustellen, dass gcc die Fehlerbehandlungsfunktion nicht einbindet, und stellt eine Reihe vonpush rbx
/...
/pop rbx
/ ... auf den schnellen Weg einiger wichtiger Kernelfunktionen, die viele Aufrufer haben und selbst nicht inline sind.Für praktische Zwecke
uint_fast32_t
ist völlig nutzlos. Es ist auf der am weitesten verbreiteten Plattform (x86_64) falsch definiert und bietet nirgendwo wirklich Vorteile, es sei denn, Sie haben einen Compiler von sehr geringer Qualität. Konzeptionell ist es nie sinnvoll, die "schnellen" Typen in Datenstrukturen / Arrays zu verwenden. Alle Einsparungen, die Sie durch die effizientere Bearbeitung des Typs erzielen, werden durch die Kosten (Cache-Fehler usw.) für die Vergrößerung von in den Schatten gestellt Ihr Arbeitsdatensatz. Und für einzelne lokale Variablen (Schleifenzähler, Temps usw.) kann ein Nicht-Spielzeug-Compiler normalerweise nur mit einem größeren Typ im generierten Code arbeiten, wenn dies effizienter ist, und nur dann auf die Nenngröße kürzen, wenn dies für die Korrektheit erforderlich ist (und mit signierte Typen, es ist nie notwendig).Die eine Variante, die theoretisch nützlich ist
uint_least32_t
, ist , wenn Sie in der Lage sein müssen, einen beliebigen 32-Bit-Wert zu speichern, aber auf Maschinen portabel sein möchten, denen ein 32-Bit-Typ mit exakter Größe fehlt. In der Praxis ist dies jedoch kein Grund zur Sorge.quelle
Nach meinem Verständnis sollte
int
es sich ursprünglich um einen "nativen" Integer-Typ mit zusätzlicher Garantie handeln, dass er mindestens 16 Bit groß sein sollte - etwas, das damals als "vernünftige" Größe galt.Als 32-Bit-Plattformen häufiger wurden, können wir sagen, dass sich die "angemessene" Größe auf 32 Bit geändert hat:
int
auf allen Plattformen.int
mindestens 32 Bit.int
der garantiert genau 32 Bit beträgt.Aber als die 64-Bit-Plattform zur Norm wurde, wurde niemand
int
zu einer 64-Bit-Ganzzahl erweitert, weil:int
der Größe von 32 Bit ab.int
in den meisten Fällen möglicherweise nicht zumutbar, da in den meisten Fällen die verwendeten Zahlen viel kleiner als 2 Milliarden sind.Nun, warum wollen Sie
uint32_t
zuuint_fast32_t
? Aus dem gleichen Grund verwenden Sprachen, C # und Java immer Ganzzahlen mit fester Größe: Der Programmierer schreibt keinen Code und denkt über mögliche Größen unterschiedlicher Typen nach. Er schreibt für eine Plattform und testet Code auf dieser Plattform. Der größte Teil des Codes hängt implizit von bestimmten Größen der Datentypen ab. Und deshalbuint32_t
ist es in den meisten Fällen eine bessere Wahl - es lässt keine Unklarheiten hinsichtlich seines Verhaltens zu.Ist es
uint_fast32_t
wirklich der schnellste Typ auf einer Plattform mit einer Größe von 32 Bit oder mehr? Nicht wirklich. Betrachten Sie diesen Code-Compiler von GCC für x86_64 unter Windows:extern uint64_t get(void); uint64_t sum(uint64_t value) { return value + get(); }
Die generierte Baugruppe sieht folgendermaßen aus:
push %rbx sub $0x20,%rsp mov %rcx,%rbx callq d <sum+0xd> add %rbx,%rax add $0x20,%rsp pop %rbx retq
Wenn Sie nun
get()
den Rückgabewert aufuint_fast32_t
(unter Windows x86_64 4 Byte) ändern , erhalten Sie Folgendes:push %rbx sub $0x20,%rsp mov %rcx,%rbx callq d <sum+0xd> mov %eax,%eax ; <-- additional instruction add %rbx,%rax add $0x20,%rsp pop %rbx retq
Beachten Sie, dass der generierte Code fast identisch ist, mit Ausnahme zusätzlicher
mov %eax,%eax
Anweisungen nach dem Funktionsaufruf, mit denen der 32-Bit-Wert in einen 64-Bit-Wert erweitert werden soll.Es gibt kein solches Problem, wenn Sie nur 32-Bit-Werte verwenden, aber Sie werden wahrscheinlich solche mit
size_t
Variablen verwenden (Array-Größen wahrscheinlich?) Und diese sind 64-Bit auf x86_64. Unter Linux sinduint_fast32_t
es 8 Bytes, daher ist die Situation anders.Viele Programmierer verwenden,
int
wenn sie einen kleinen Wert zurückgeben müssen (sagen wir im Bereich [-32,32]). Dies würde perfekt funktionieren, wennint
es sich um eine native Ganzzahlgröße für Plattformen handelt. Da es sich jedoch nicht um eine 64-Bit-Plattform handelt, ist ein anderer Typ, der dem nativen Plattformtyp entspricht, die bessere Wahl (es sei denn, er wird häufig mit anderen Ganzzahlen kleinerer Größe verwendet).Grundsätzlich ist, unabhängig davon, was der Standard sagt,
uint_fast32_t
bei einigen Implementierungen ohnehin ein Fehler aufgetreten. Wenn Sie sich für zusätzliche Anweisungen interessieren, die an einigen Stellen generiert werden, sollten Sie Ihren eigenen "nativen" Ganzzahltyp definieren. Oder Sie können essize_t
für diesen Zweck verwenden, da es normalerweise dernative
Größe entspricht (ich schließe keine alten und undurchsichtigen Plattformen wie 8086 ein, sondern nur Plattformen, auf denen Windows, Linux usw. ausgeführt werden können).Ein weiteres Zeichen, das anzeigt, dass
int
es sich um einen nativen Ganzzahltyp handeln sollte, ist die "Ganzzahl-Heraufstufungsregel". Die meisten CPUs können nur Operationen auf nativen Systemen ausführen, sodass 32-Bit-CPUs normalerweise nur 32-Bit-Additionen, Subtraktionen usw. ausführen können (Intel-CPUs sind hier eine Ausnahme). Ganzzahlige Typen anderer Größen werden nur durch Lade- und Speicheranweisungen unterstützt. Beispielsweise sollte der 8-Bit-Wert mit der entsprechenden Anweisung "8-Bit-Vorzeichen laden" oder "8-Bit-Vorzeichen laden" geladen werden und wird nach dem Laden auf 32 Bit erweitert. Ohne die Ganzzahl-Heraufstufungsregel müssten C-Compiler etwas mehr Code für Ausdrücke hinzufügen, die Typen verwenden, die kleiner als der native Typ sind. Leider gilt dies bei 64-Bit-Architekturen nicht mehr, da Compiler in einigen Fällen zusätzliche Anweisungen ausgeben müssen (wie oben gezeigt).quelle
uint_fast32_t
zu, die unter allen, aber sehr ausgewählten Umständen nicht eindeutig ist. Ich vermute, der treibende Grund dafüruint_fastN_t
ist, dass wir "nichtunsigned
als 64-Bit verwenden, obwohl es auf einer neuen Plattform oft am schnellsten ist, weil zu viel Code kaputt geht", aber "Ich möchte immer noch einen schnellen, mindestens N-Bit-Typ . " Ich würde dich wieder UV, wenn ich könnte.int
64-Bit und normalerweise einen Geschwindigkeitsverlust durch den Cache-Footprint geben.int
64-Bit erstellt haben, benötigt Ihruint32_t
Typedef mit fester Breite einen__attribute__
oder einen anderen Hack oder einen benutzerdefinierten Typ, der kleiner als istint
. (Odershort
, aber dann haben Sie das gleiche Problemuint16_t
.) Niemand will das. 32-Bit ist breit genug für fast alles (im Gegensatz zu 16-Bit); Die Verwendung von 32-Bit-Ganzzahlen, wenn dies alles ist, was Sie benötigen, ist auf einem 64-Bit-Computer in keiner sinnvollen Weise "ineffizient".In vielen Fällen, wenn ein Algorithmus mit einem Array von Daten arbeitet, besteht der beste Weg zur Verbesserung der Leistung darin, die Anzahl der Cache-Fehler zu minimieren. Je kleiner jedes Element ist, desto mehr können in den Cache passen. Aus diesem Grund wird immer noch viel Code für die Verwendung von 32-Bit-Zeigern auf 64-Bit-Computern geschrieben: Sie benötigen keine Daten in der Nähe von 4 GB, aber die Kosten für die Erstellung aller Zeiger und Offsets erfordern acht Bytes anstelle von vier wäre erheblich.
Es gibt auch einige ABIs und Protokolle, die genau 32 Bit benötigen, z. B. IPv4-Adressen. Das
uint32_t
bedeutet wirklich: Verwenden Sie genau 32 Bit, unabhängig davon, ob dies für die CPU effizient ist oder nicht. Diese wurden früher alslong
oder deklariertunsigned long
, was während des 64-Bit-Übergangs viele Probleme verursachte. Wenn Sie nur einen vorzeichenlosen Typ benötigen, der Zahlen bis zu mindestens 2³²-1 enthält, ist dies die Definitionunsigned long
seit Erscheinen des ersten C-Standards. In der Praxis ging jedoch genug alter Code davon aus, dass along
einen Zeiger oder einen Dateiversatz oder einen Zeitstempel enthalten könnte, und genug alter Code davon aus, dass er genau 32 Bit breit war, sodass Compiler nicht unbedingtlong
das Gleiche tun können,int_fast32_t
ohne zu viel Material zu beschädigen.Theoretisch wäre die Verwendung eines Programms zukunftssicherer
uint_least32_t
und möglicherweise sogaruint_least32_t
Elementeuint_fast32_t
für Berechnungen in eine Variable zu laden . Eine Implementierung, die überhaupt keinenuint32_t
Typ hatte, konnte sich sogar als formal konform mit dem Standard deklarieren! (Es wäre einfach nicht in der Lage sein , viele bestehenden Programme zu kompilieren.) In der Praxis gibt es keine Architektur mehr woint
,uint32_t
unduint_least32_t
ist nicht gleich, und kein Vorteil, zur Zeit , zu der Leistunguint_fast32_t
. Warum also zu komplizierte Dinge?Schauen Sie sich jedoch den Grund an, warum alle
32_t
Typen existieren mussten, als wir sie bereits hattenlong
, und Sie werden sehen, dass diese Annahmen in unseren Gesichtern bereits in die Luft gesprengt wurden. Ihr Code wird möglicherweise eines Tages auf einem Computer ausgeführt, auf dem 32-Bit-Berechnungen mit exakter Breite langsamer sind als die native Wortgröße, und Sie wären besser dran gewesenuint_least32_t
für die Speicherung unduint_fast32_t
für religiöse Berechnungen zu verwenden. Oder wenn Sie diese Brücke überqueren, wenn Sie dazu kommen und nur etwas Einfaches wollen, gibt esunsigned long
.quelle
int
nicht 32 Bit, z. B. ILP64. Nicht dass sie üblich wären.int
ist diese 32 Bit breit. Der TypMKL_INT
wird sich ändern.Um eine direkte Antwort zu geben: Ich denke, der wahre Grund, warum
uint32_t
überuint_fast32_t
oderuint_least32_t
einfach verwendet wird, ist, dass es einfacher zu tippen ist und aufgrund der Kürzung viel besser zu lesen ist: Wenn Sie Strukturen mit einigen Typen erstellen, und einige von ihnen sinduint_fast32_t
oder ähnlich, dann ist es oft schwierig, sie gut mitint
oderbool
oder anderen Typen in C auszurichten , die ziemlich kurz sind (Beispiel:char
vs.character
). Ich kann dies natürlich nicht mit harten Daten belegen, aber die anderen Antworten können auch nur den Grund erraten.Aus technischen Gründen
uint32_t
gibt es meines Erachtens keine - wenn Sie unbedingt ein genaues 32-Bit-Int ohne Vorzeichen benötigen , ist dieser Typ Ihre einzige standardisierte Wahl. In fast allen anderen Fällen sind die anderen Varianten technisch vorzuziehen - insbesondere,uint_fast32_t
wenn Sie sich Gedanken über die Geschwindigkeit und denuint_least32_t
Speicherplatz machen. Mituint32_t
in beiden Fällen der Fähigkeit , Risiken zu kompilieren nicht als der Typ nicht existieren erforderlich ist.In der Praxis existieren die
uint32_t
und verwandte Typen auf allen aktuellen Plattformen, mit Ausnahme einiger sehr seltener (heutzutage) DSPs oder Scherzimplementierungen, sodass bei der Verwendung des genauen Typs nur ein geringes tatsächliches Risiko besteht. Während Sie mit den Typen mit fester Breite auf Geschwindigkeitseinbußen stoßen können, sind sie (bei modernen CPUs) nicht mehr lähmend.Aus diesem Grund, denke ich, gewinnt der kürzere Typ in den meisten Fällen einfach aufgrund der Faulheit des Programmierers.
quelle