Laufzeit sin mit march=native komisch



  • 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.1

    Kompiliert 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.8

    Spaß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 irgendwelche volatile -Tricks fast alle Compiler nur Code erzeugen, der das Endergebnis ausgibt.

    Allerdings sind Compiler-Optimierungen auch nie wirklich gut vorhersehbar 😉


  • Mod

    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)


  • Mod

    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.


Anmelden zum Antworten