Laufzeit sin mit march=native komisch
-
Ich kann das hier mit Intel(R) Core(TM) i5-3337U CPU @ 1.80GHz reproduzieren, und zwar sowohl mit g++5.4 als auch clang-3.9.
Zu Arcoths Frage: kompiliert und Zeit gemessen habe ich mit
g++ -O3 (-march=native) archperformance.cpp && time ./a.out
Wobei man den Unterschied zwischen 1,5s und 9s bzw. zwischen 4s und 12s auch komplett ohne "time" merkt. Diese Messung ist trivial. Da der Unterschied so groß ist, ist hier auch kein besonderes Messtool nötig und 1x messen reicht auch aus.
Ich habe raus bei:
Zeit=user, nMessungen=1 Zeiten: float / float,native ; double / double,native // std::sin((j+i)*fac); g++: 1,52s / 8,95s ; 12,03s / 4,17s clang: 1,45s / 8,98s ; 11,98s / 4,27s
-
Hallo
Meine Messungen für
sum += std::sin((j+i)*fac);
:Compiler float float/native double double/native g++ 0.884s 0.888s 1.826s 1.828s clang 0.909s 1.247s 1.807s 1.777s
CPU: i7-6800k @ 4.0GHz
GCC: 7.1.1
Clang: 4.0.1Kompiliert habe ich jeweils mit
-O3
und gemessen habe ich auch mit time.Scheint, als ob das ein GCC-Bug ist, der in der neueren Version gefixed wurde.
LG
-
Hi, ich habe ähnlich wie wob nur:
g++ (-march=native) archperformance.cpp && time ./a.out
gemacht (g++ 5.4). Mit den Optionen O3 oder "std=c++xx" hatte ich auch rumprobiert aber die hatten keine signifikanten Anderungen bewirkt.
Fytch schrieb:
Scheint, als ob das ein GCC-Bug ist, der in der neueren Version gefixed wurde.
Oder bei neueren CPU's. Hmm, apt-get geht bei mir nur bis gcc-6-base und g++-5.
-
Kann ich dem Kompiler sagen, dass er bei einer Codezeile (sin) march=native nicht machen soll?
-
std::sin((j+i)*fac): float / float,native ; double / double,native g++: 5,21s / 4,23s ; 5,08s / 4,23s clang: 5,24s / 4,97s ; 5,08s / 4,36s
CPU: i5-6200U
GCC: 5.4.0
Clang: 3.8Spaßeshalber noch in einer VM mit GCC7 und Clang4 ausprobiert:
std::sin((j+i)*fac): float / float,native ; double / double,native g++: 2,90s / 2,87s ; 3,04s / 3,13s clang: 3,13s / 2,96s ; 2,99s / 3,07s
GCC: 7.1.1
Clang: 4.0.1
-
Also scheinbar doch die CPU. Danke für die ganzen Tests. Mit clang 4.0.0 bekomme ich die selben Werte wie mit g++5.4.
-
mit g++7.1 auch so.
sum += std::sin((j+i)*fac); // 1.7 7.2 9.0 3.5 //g++ 5.4 sum += std::sin((j+i)*fac); // 2.8 7.0 9.0 3.2 //g++ 7.1
-
Trotz allem wundert mich bei dem Code doch ein wenig, dass hier überhaupt eine Zeit gemessen werden kann.
Meine Erfahrungen mit subjektiv komplizierteren Berechnungen hätten mich jetzt darauf tippen lassen, dass hier
ohne irgendwelchevolatile
-Tricks fast alle Compiler nur Code erzeugen, der das Endergebnis ausgibt.Allerdings sind Compiler-Optimierungen auch nie wirklich gut vorhersehbar
-
Hier scheint eine spezifische Eigenheit von SandyBridge/IvyBridge-Prozessoren zuzuschlagen.
#include <iostream> #include <cmath> int main(){ const int hm = 10000; double sum = 0; T fac = 0.815; for (int i = 0; i<hm;i++) for (int j = 0; j<hm;j++) sum += [&]() { auto res = F; #if VZEROUPPER asm volatile("vzeroupper":::"memory"); #endif return res; }(); std::cout << sum << '\t'; }
mit
for y in "sin((j+i))" "sin((j+i)*fac)" "std::sin((j+i))" "std::sin((j+i)*fac)" "exp((j+i))" "exp((j+i)*fac)" "std::exp((j+i))" \ "std::exp((j+i)*fac)"; do for x in float double; do for a in "" "-march=native"; do for z in 0 1; do echo $y $x $a $z; \ clang++ -std=c++1z -O3 $a main.cpp -DT=$x -DF=$y -DVZEROUPPER=$z && /usr/bin/time -f "%e" ./a.out; done; done; done; done
Mit vzeroupper verschwinden die Unterschiede zwischen mit und ohne "-march=native", z.T. sind sogar beide Varianten schneller.
Der Code, den Compiler erzeugt, je nachdem, ob avx erlaubt ist oder nicht, ist praktisch identisch, mit -mavx benutzt der Compiler die VEX-kodierten Pendants zum normalen SSE-Code. Offenbar hinterlassen die transzendenten Funktionen (aus glibc) z.T. Rückstände in den oberen Häften der ymm-Register, was bei den angesprochenen Prozessoren zu Verzögerungen führen kann.
-
Oh, Danke für die informative Antwort.
Könnte man statt dessen die sin/exp Funktion auf allen Registern rechnen lassen? (#pragma omp simd hatte keine Wirkung gezeigt). Wenn man das tut müsste der Unterschied dann nicht auftreten, oder?statt
sum += [&]() { auto res = F; #if VZEROUPPER asm volatile("vzeroupper":::"memory"); #endif return res; }();
konnte ich auch einfach
{ sum+=F; #if VZEROUPPER asm volatile("vzeroupper":::"memory"); #endif }
(alternativ auch erst VZEROUPPER und dann sum+=F)
schreiben. Macht das einen Unterschied?Wenn man das nutzt, geht es für Sandy Bridge, Ivy Bridge, Haswell, und Broadwell aber scheint für Knights Landing wiederum langsammer zu sein...
(http://www.agner.org/optimize/blog/read.php?i=789)
-
Sinativ schrieb:
Könnte man statt dessen die sin/exp Funktion auf allen Registern rechnen lassen?
Heisst was?
Sinativ schrieb:
Macht das einen Unterschied?
Kaum. Du kannst es auch in die äußere Schleife verschieben. Offenbar hinterlassen die Funktionen nur in seltenen Fällen Mist in den ymm-Registern.
-
Heisst was?
Das es 2,4 bzw. 8 Rechnungen gleichzeitig macht (wie bei mult und add möglich) und alle Register (einschließlich ymm-Registern) genutzt werden, so dass die automatisch überschrieben werden.