Der C-Standard ist bezüglich der Typenfamilie uint_fast * _t ziemlich unklar. Auf einem System gcc-4.4.4 linux x86_64 sind die Typen uint_fast16_t und uint_fast32_t beide 8 Bytes groß. Die Multiplikation von 8-Byte-Zahlen scheint jedoch ziemlich langsamer zu sein als die Multiplikation von 4-Byte-Zahlen. Der folgende Codeabschnitt zeigt Folgendes:
%Vor%Wenn ich den Befehl time für das Programm ausführe, bekomme ich
%Vor%Wenn ich den Typ in uint_fast16_t (und den printf-Modifizierer) ändere, wird das Timing zu
%Vor%Wäre es also nicht viel besser, wenn der Header stdint.h uint_fast16_t (und auch uint_fast32_t) als 4-Byte-Typ definiert wäre?
AFAIK-Compiler definieren nur ihre eigenen Versionen von (u)int_(fast/least)XX_t
-Typen, wenn diese nicht bereits vom System definiert sind. Das liegt daran, dass es sehr wichtig ist, dass diese Typen in allen Bibliotheken / Binärdateien auf einem einzigen System gleich definiert sind. Andernfalls, wenn andere Compiler diese Typen anders definieren würden, könnte eine mit CompilerA erstellte Bibliothek einen anderen uint_fast32_t
-Typ haben als eine mit CompilerB erstellte Binärdatei, aber diese Binärdatei kann immer noch mit der Bibliothek verlinken; Es gibt keine
Ich denke, dass eine solche Designentscheidung nicht einfach zu treffen ist. Es hängt von vielen Faktoren ab. Für den Moment nehme ich Ihr Experiment nicht als schlüssig, siehe unten.
Zunächst einmal gibt es kein one einzelnes Konzept dessen, was fast bedeuten sollte. Hier betonten Sie die Multiplikation an Ort und Stelle, die nur ein bestimmter Gesichtspunkt ist.
Dann ist x86_64 eine Architektur und kein Prozessor. Daher könnten die Ergebnisse für die verschiedenen Prozessoren in dieser Familie sehr unterschiedlich sein. Ich glaube nicht, dass es vernünftig wäre, wenn gcc die Art der Entscheidung von bestimmten Befehlszeilenschaltern abhängig machen würde, die für einen bestimmten Prozessor optimiert sind.
Kommen Sie nun zu Ihrem Beispiel zurück. Ich nehme an, Sie haben sich auch den Assembler Code angeschaut? Hat es beispielsweise SSE-Anweisungen verwendet, um Ihren Code zu realisieren? Hast du prozessorspezifische Optionen aktiviert, etwa -march=native
?
Bearbeiten: Ich habe ein wenig mit Ihrem Testprogramm experimentiert und wenn ich es genau so belasse, wie es ist, kann ich Ihre Messungen grundsätzlich reproduzieren. Aber ich modifiziere und spiele damit herum und bin noch weniger davon überzeugt, dass es schlüssig ist.
Wenn z. B. die innere Schleife geändert wird, um nach unten zu gehen, sieht der Assembler fast genauso aus wie vorher (aber mit Dekrement und einem Test gegen 0), aber die Ausführung dauert ungefähr 50% mehr. Ich denke also, das Timing hängt sehr stark von der Umgebung der Instruktion ab, die Sie benchmarken wollen, Pipeline-Stände, was auch immer. Sie müssten Bankcodes sehr unterschiedlicher Art erstellen, wo die Anweisungen in verschiedenen Kontexten ausgegeben werden, Ausrichtungsprobleme und Vektorisierung spielen, um eine Entscheidung zu treffen, welche die geeigneten Typen für die fast
typedef
s sind.
Die tatsächliche Leistung zur Laufzeit ist ein sehr kompliziertes Thema. Mit vielen Faktoren von RAM-Speicher, Festplatten, OS'es; Und die vielen prozessorspezifischen Macken. Aber das wird dir einen schweren Schlag versetzen:
N_fastX_t
N_leastX_t
Das Multiplikationsproblem?
Auch um zu beantworten, warum die größere fastX-Variable in der Multiplikation langsamer wäre. Ursache ist aufgrund der Natur der Multiplikation. (ähnlich dem, was man in der Schule dachte)
%Vor%Es ist jedoch wichtig zu wissen, dass ein Computer ein "logischer Idiot" ist. Während es für uns Menschen offensichtlich ist, den abschließenden Nullenschritt zu überspringen. Der Computer wird es immer noch herausfinden (es ist billiger, als es konditional zu überprüfen und dann trotzdem zu arbeiten). Daraus ergibt sich eine Eigenart für eine größere Variable mit dem gleichen Wert
%Vor%Während ich die restlichen 0x0-Zusätze im Multiplikationsprozess nicht spammte. Es ist wichtig zu beachten, dass der Computer "sie erledigt". Und daher ist es natürlich, dass eine größere variable Multiplikation längere Zeit braucht als ihr kleineres Gegenstück. (Daher ist es immer gut, Multiplikationen und Divisionen zu vermeiden, wann immer es möglich ist).
Aber hier kommt die zweite Eigenart. Es kann nicht für alle Prozessoren gelten. Es ist wichtig zu beachten, dass alle CPU-Operationen in CPU-Zyklen gezählt werden. In dem in jedem Zyklus Dutzende (oder mehr) solcher kleinen Additionsoperationen durchgeführt werden, wie oben gesehen. Als Ergebnis kann eine 8-Bit-Addition die gleiche Zeit wie eine 8-Bit-Multiplikation usw. annehmen. Aufgrund der verschiedenen Optimierungen und CPU-spezifischen Macken.
Wenn es dich so betrifft. Gehen Sie zu Intel: Ссылка
Zusätzliche Erwähnung von CPU vs RAM
Da die CPU nach Moore's Gesetz mehrere Male schneller ist als Ihr DDR3 RAM.
Dies kann zu Situationen führen, in denen mehr Zeit damit verbracht wird, die Variable vom RAM nachzuschauen und dann von der CPU "zu berechnen". Dies ist in langen Zeigerketten am ausgeprägtesten.
Also, während auf dem meisten Prozessor ein CPU-Cache existiert, um die "RAM Look-Up" -Zeit zu reduzieren. Seine Verwendung ist auf bestimmte Fälle beschränkt (wobei die Cache-Zeile am meisten profitiert). Und für Fälle, in denen es nicht passt. Beachten Sie, dass die RAM-Nachschlagzeit & gt; CPU-Verarbeitungszeit (ohne Multiplikation / Divisionen / einige Macken)
Nur weil ich neugierig auf die schnellen Integer-Typen war, habe ich einen echten Parser getestet, der in seinem semantischen Teil einen ganzzahligen Typ für die Indexierung von Arrays und C ++ - Containern verwendet hat. Es führte eine Mischung von Operationen statt einer einfachen Schleife durch und das meiste Programm war nicht von dem gewählten Integertyp abhängig. Tatsächlich wäre für meine speziellen Daten jeder Integer-Typ in Ordnung gewesen. So haben alle Versionen die gleiche Ausgabe produziert.
Auf Assemblerebene gibt es 8 Fälle: vier für die Größen und 2 für die Signiertheit. Die 24 ISO C-Typnamen müssen den acht Grundtypen zugeordnet werden. Wie Jens bereits feststellte, muss ein "gutes" Mapping den jeweiligen Prozessor und den jeweiligen Code berücksichtigen. Daher sollten wir in der Praxis keine perfekten Ergebnisse erwarten, obwohl die Compilerschreiber den generierten Code kennen sollten.
Viele Läufe des Beispiels wurden gemittelt, so dass der Fluktuationsbereich der Laufzeit nur etwa 2 der kleinsten gegebenen Ziffer ist. Für diese spezielle Konfiguration waren die Ergebnisse:
int16_t
/ uint16_t
bzw. int64_t
/ uint64_t
. int8_t
/ uint8_t
bzw. int32_t
/ uint32_t
. Compiler: g ++ 4.9.1, Optionen: -O3 mtune = generic -march = x86-64
CPU: Intel ™ Kern ™ 2 Duo E8400 @ 3.00GHz
Die Zuordnung
%Vor%Größen und Timings
%Vor% Ja, ich denke, das ist einfach ein Fehler. Leider kann man nicht einfach so Fehler beheben, ohne die ABI zu brechen, aber das spielt keine Rolle, da praktisch niemand (und sicherlich keine Bibliotheksfunktionen, die ich kenne) tatsächlich die *int_fast*_t
-Typen verwendet.