Momentan wundere ich mich über GCC in zwei Punkten:
1) habe ich eine Rechnung in einer Schleife von double auf int
umgestellt. Dabei erhöht sich die Rechenzeit von 220 auf 590 mSec.
Eigentlich hätte ich eine Beschleunigung erwartet. int, uint32_t und
uint_fast_t sind praktisch gleich. Gibt es für die Verlangsamung eine
plausible Erklärung oder lohnt es sich da noch mal genauer nachzuschauen
? GCC compiliert -o3
2) bei int = double gibt es beim Compilieren keine Typwarnung. Kann man
die überhaupt abstellen oder muß da was oberfaul sein ?
Gib doch mal
- die Zielhardware
- die 2...3 Programmversionen
- die Compileroptionen, insbesondere die zu Optimierung, aber auch
Bibliotheken
- die Art der Zeitmessung
an.
Ansonsten lautet die Antwort : 42
Wie fop schon gesagt hat: Viel zu wenig Informationen.
Normalerweise sollte man davon ausgehen, dass die Integerrechnung
schneller vonstatten geht als die mit double's, aber vielleicht muss
dafür, an anderer Stelle, ständig von integer auf double konvertiert
werden.
Amateur schrieb:> Normalerweise sollte man davon ausgehen, dass die Integerrechnung> schneller vonstatten geht als die mit double's
Der Thread steht in "PC Programmierung" und bei x86 aus diesem
Jahrtausend gilt diese Regel nicht. Da ist Fliesskommaverarbeitung sehr
schnell und z.B. bei AMD getrennt von der Integerverarbeitung.
Der Teufel kann allerdings im Detail stecken, weshalb man dafür
minimalisierte aber funktionsfähige Testprogramme benötigt.
Ok, es ist ein Ubuntu 18.04 LTS auf einer HP Z440 Workstation mit 8
Kernen.
(deshalb auch unter PC Programmierung gepostet). GCC ist mit 7.4.0 schon
etwas altbacken ist halt bei der LTS dabei und compiliert als Release
mit -o3. Die Zeitmessung halte ich für seriös und mache ich mit dem
Systemtimer wobei uS wegfallen und mS auf plusminus 1% reproduzierbar
sind
Die Rechnung ist eine Parallelschaltung mit vielen Iterationen:
1
tmp_result=
2
(int_lut[j]*int_lut[i])/
3
(int_lut[j]+int_lut[i]);
4
tmp_result-=intTargetR;
5
if(abs(tmp_result)<(abs(best4R))
Die 220mS double Version sieht so aus
1
tmp_result=
2
(comb_lut[j].e_value*comb_lut[i].e_value)/
3
(comb_lut[j].e_value+comb_lut[i].e_value);
4
tmp_result-=TargetR;
5
6
if(abs(tmp_result)<(abs(best4R)))
Der if Zweig trifft selten zu und trägt damit nicht zur Laufzeit bei.
Interessanterweise wird die Laufzeit bei double ähnlich schlecht wenn
ich die 1/R=1/Ra+1/Rb Formel nehme.
daneben hätte ich erwartet, dass ein Zugriff auf array of struct länger
dauert als auf ein int_array aber es ist genau gleich was für einen
guten Optimizer spricht.
Aber die Anzahl der Iterationen ist gleich? Ich nehm mal an du hast mit
220ms/590ms nicht nur einen Schleifendurchgang gemessen. Feste Anzahl an
Iterationen oder gibt es eine Abbruchbedingung die bedingt durch den
Datentp früher oder später abbricht?
Fliesskomma Rechnung ist in einer FPU schon sehr schnell, aber eben
keine Integergeschwindigkeit. Da fehlt schon noch ein Stueck.
Vielleicht nicht ganz unwichtig... Eine gleich grosse Integer Zahl hat
eine hoehere Genauigkeit wie eine Floatzahl.
Was macht der denn da für Instruktionen draus? Vielleicht wird der
double Code irgendwie autovektorisiert oder sowas.
Hier kann man sowas austesten:
https://godbolt.org/
Danke für das hübsche Spielzeug. Damit brauch ich nicht an cmake
rumbasteln. Ich muss mir noch etwas Zeit nehmen das ganze weiter zu
isolieren. Wenn ich zwischen den Zeilen lese sollte sich eine nähere
Betrachtung doch lohnen da auch andere hier erwarten daß int schneller
als double sein sollte.
J. V. schrieb:> Danke für das hübsche Spielzeug.
Ganz ehrlich: Wer Mikro-Optimierungen betrieben will, und über
cache-misses schwadroniert, sollte in der Lage sein, seiner Toolchain
Disassemblies zu entlocken, und die auch zu verstehen.
Oliver
macht gcc/cc von sich aus Gebrauch von AVX/SSEx ?
Ich würde gern mal das Compilat des Schnipsels sehen von beiden
Versionen.
So ein Gedanke: Fliesskomma Richtung SSE auslagern und Integer auf dem
Standardbefehlssatz.
Dennis H. schrieb:> macht gcc/cc von sich aus Gebrauch von AVX/SSEx ?
Muss er halt nachschauen, wie sein gcc configuriert ist. Üblicherweise
erstellt der generischen (ARM64)-Code.
Wer aber auf dem level optimiert, soellte -march=native und vielleicht
auch noh ein paar weitere Optionen nutzen.
Oliver
J.V.:
Haste schonmal auf so einer Seite geschaut ?
https://docs.microsoft.com/de-de/cpp/intrinsics/x64-amd64-intrinsics-list?view=vs-2019
Da kann man noch einiges Rauskitzeln mit direkten SSE-Befehlen in C.
Erfreulicherweise gibt es dann Datentypen die das Handhaben
vereinfachen.
In der Liste fehlen jetzt leider die AVX512-Befehle.
Ich habe das mal mit Anwendung auf ein Numpy-Array genutzt. Das
Numpy-Eigene OR war mir zu langsam. Mit
__m256i* pnt=PyArray_DATA(in_array);
__m256i q0,q1,q2,q3;
uint64_t t=PyArray_SIZE(in_array)/16/4/2;
while(t--) {
q0=*pnt;
q1=*(pnt+1);
q2=*(pnt+2);
q3=*(pnt+3);
*pnt++=_mm256_or_si256(q0,p);
*pnt++=_mm256_or_si256(q1,p);
*pnt++=_mm256_or_si256(q2,p);
*pnt++=_mm256_or_si256(q3,p);
}
gings direkt 4x so schnell.
Oliver S. schrieb:> Wer aber auf dem level optimiert, soellte -march=native und vielleicht> auch noh ein paar weitere Optionen nutzen.Oliver S. schrieb:> Ganz ehrlich: Wer Mikro-Optimierungen betrieben will, und über> cache-misses schwadroniert,
Halt mal etwas den Ball flach.
Ich habe eher das Gefühl der TE interessiert sich einfach dafür was da
genau los ist.
Er ist zufällig über eine Merkwürdigkeit gestolpert und will das nun
genauer untersuchen.
Und auch die Mitleser (inklusive mir) scheinen das interessant zu finden
weil das beobachtete Verhalten erstmal ungewöhnlich scheint.
Von einem zwingenden (Mikro-) Optimierungsbedarf lese ich da nichts.
Mein erster Verdächtiger wäre die Division.
Übersetze das Programm mal mit den Optionen -g -S und schau dir mal den
Assembler Output an. Durch die -g Option ist im Assemblertext auch die
ursprüngliche Zeilennummer enthalten, wonach du suchen kannst.
Joggel E. schrieb:> Fliesskomma Rechnung ist in einer FPU schon sehr schnell, aber eben> keine Integergeschwindigkeit.
Die Fliesskomma-Division ist heute wesentlich schneller als die
Integer-Division (seit Intel Core 2 und AMD K7).
CPU ist Haswell, dafür gilt lt. Agner Fog als Latenz:
- Integer Division u32: 22-29 Takte
- SSE Fliesskomma 32/64: 10-13 Takte
Beim Durchsatz sind es 9-11 vs 7 Takte. Das ist der Wert, nach dem die
nächste unabhängige Division gestartet werden kann.
CPU ist Haswell, dafür gilt lt. Agner Fog als Latenz:
- Integer Division u32: 22-29 Takte
- SSE Fliesskomma 32/64: 10-13 Takte
Knapp Faktor 3 könnte sogar passen. Die Laufzeit verlängert sich von
220mSec auf 580 mSec und das ist nicht von Pappe und hat wohl auch nix
mit Mikrooptimierung zu tun. (Ein Mix-Listing habe ich leider noch immer
nicht gekriegt)
J. V. schrieb:> Ok, es ist ein Ubuntu 18.04 LTS auf einer HP Z440 Workstation mit 8> Kernen.> (deshalb auch unter PC Programmierung gepostet). GCC ist mit 7.4.0 schon> etwas altbacken ist halt bei der LTS dabei
Mach mal das Ubuntu nicht schlechter als es ist:
gcc in Version 8.3 ist auch bei der 18.04 LTS als Paket zu haben.
Kann leider nicht mehr editieren da ich über timeout ausgeloggt wurde.
Die Codeunterschiede sehen gar nicht soo arg aus alsda wären
mulsd und divsd für die double Version und
imulq und idivq für die interger Version
Das ist die 64-Bit Integer Version mit Vorzeichen. Die ist noch
langsamer.
Latenz:
- Integer Division s64: 39-103 Takte
- SSE Fliesskomma 32/64: 10-13 Takte
Durchsatz:
- Integer Division s64: 24-81 Takte
- SSE Fliesskomma 32/64: 7 Takte
Gegenüber dem Haswell ist bei Integers sogar der Goldmont (Atom) in
meinem Netbook schneller (s64: 13-43, double: 34).
Also Leute, von der Idee das bei den grossen x86 Integers schneller als
Doubles seien, bitte ganz schnell abschwören. Nur bei den kleinen
stimmts. ;-)
J. V. schrieb:> daneben hätte ich erwartet, dass ein Zugriff auf array of struct> länger> dauert als auf ein int_array aber es ist genau gleich was für einen> guten Optimizer spricht.> struct r_data {> bool e_use;> std::string e_name;> double e_value;> };>> std::array<r_data,MAX_COMB> comb_lut;> std::array<int_fast32_t, MAX_COMB> int_lut;
Keine Ahnung wo dein Wissen her kommt - nach Gefühl fühlt es sich an als
wuerdest du 286/386 Erfahrungen auf aktuelle Hardware spiegeln, die
meisten deiner Vermutungen (auch in anderen Posts) sind schlicht und
einfach falsch, sorry
Deine Zeitmessung ist viel zu ungenau um diesen Unterschied zu erkennen,
selbst wenn es einen geben würde
> Keine Ahnung wo dein Wissen her kommt
Habs ja eingesehen. Beim Anschauen von /proc/cpuinfo ist klar geworden
daß bei den Cachegrößen sowieso die gesamte LUT reinpasst. Egal ob mit
oder ohne struct. Ebenso habe ich mich halt auch mit den floats
verpeilt. Auch wenn ich hier die Hocke vollkriege, war ich zumindest
nicht der Einzige der danebengeschätzt hat.