Laufzeit sin mit march=native komisch
-
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.