Laufzeit sin mit march=native komisch
-
Ich habe mal geschaut wie lange die Laufzeit eines Programmes mit der "g++ -march=native" Option. In den Kommentaren steht erst normale Zeit in Sekunden, dann mit der Option und dann, falls ein Faktor drin ist noch als double (wieder normal und mit der Option)
#include <iostream> #include <cmath> int main(){ const int hm = 10000; double sum = 0; float fac = 0.815; //oder double for (int i = 0; i<hm;i++) for (int j = 0; j<hm;j++) //normal -march=native | double: native //sum += sin((j+i)); // 9.0 3.5 //sum += sin((j+i)*fac); // 9.0 3.7 3.5 3.5 //sum += std::sin((j+i)); // 9.5 3.8 //sum += std::sin((j+i)*fac); // 1.7 7.2 9.0 3.5 //sum += exp((j+i)); // 6.2 10.8 //sum += exp((j+i)*fac); // 6.4 11.0 6.4 10.9 //sum += std::exp((j+i)); // 6.0 11.0 //sum += std::exp((j+i)*fac); // 1.9 6.3 6.4 10.9 std::cout << sum <<std::endl; }
Kann mir jemand sagen warum "march=native" manchmal besser ist und manchmal nicht? std::sin besonders interessant mit float ist ohne besser mit double ist mit besser. Als Alternative zum Faktor kann man es auch einfach casten.
-
Zuallererst die obligatorische Frage: Wie hast du gemessen? Aussagekräftige Messung ist nicht direkt trivial. Sind Optimierungsflags mitgegeben worden?
Und dazu sollte natürlich erwähnt werden, dass ein Optimizer keine Garantien bezüglich Laufzeiten macht, sondern nur über die Semantik des produzierten Codes. Ich kenne keine Statistiken, aber es wäre nicht das erste mal, dass eine Optimierung sich negativ auswirkt (bzw. unter gewissen Umständen, oder dass es so scheint).
-
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.