Warum ist die gcc-Mathematikbibliothek so ineffizient?

Lesezeit: 5 Minuten

Benutzer-Avatar
Benjamin Batistic

Als ich etwas Fortran-Code nach C portierte, überraschte es mich, dass der größte Unterschied in der Ausführungszeit zwischen dem mit ifort (Intel Fortran-Compiler) kompilierten Fortran-Programm und dem mit gcc kompilierten c-Programm von den Auswertungen trigonometrischer Funktionen herrührt (sin, cos). Es hat mich überrascht, weil ich früher geglaubt habe, was diese Antwort erklärt, dass Funktionen wie Sinus und Cosinus in Mikrocode in Mikroprozessoren implementiert sind.

Um das Problem deutlicher zu erkennen habe ich ein kleines Testprogramm in Fortran erstellt

program ftest
  implicit none
  real(8) :: x
  integer :: i
  x = 0d0
  do i = 1, 10000000
    x = cos (2d0 * x)
  end do
  write (*,*) x
end program ftest

An intel Q6600 Prozessor u 3.6.9-1-ARCH x86_64 Linux
Ich komme mit ifort version 12.1.0

$ ifort -o ftest ftest.f90 
$ time ./ftest
  -0.211417093282753     

real    0m0.280s
user    0m0.273s
sys     0m0.003s

während mit gcc version 4.7.2 Ich bekomme

$ gfortran -o ftest ftest.f90 
$ time ./ftest
  0.16184945593939115     

real    0m2.148s
user    0m2.090s
sys     0m0.003s

Das ist fast ein Faktor 10 Unterschied! Kann ich noch glauben, dass die gcc-Implementierung von cos ist ein Wrapper um die Mikroprozessorimplementierung in ähnlicher Weise wie dies wahrscheinlich in der Intel-Implementierung erfolgt? Wenn das stimmt, wo ist der Flaschenhals?

BEARBEITEN

Laut Kommentaren sollten aktivierte Optimierungen die Leistung verbessern. Meine Meinung war, dass Optimierungen die Bibliotheksfunktionen nicht beeinflussen … was nicht bedeutet, dass ich sie nicht in nicht trivialen Programmen verwende. Hier sind jedoch zwei zusätzliche Benchmarks (jetzt auf meinem Heimcomputer intel core2)

$ gfortran -o ftest ftest.f90
$ time ./ftest
  0.16184945593939115     

real    0m2.993s
user    0m2.986s
sys     0m0.000s

und

$ gfortran -Ofast -march=native -o ftest ftest.f90
$ time ./ftest
  0.16184945593939115     

real    0m2.967s
user    0m2.960s
sys     0m0.003s

Welche speziellen Optimierungen hatten Sie (Kommentatoren) im Sinn? Und wie kann der Compiler in diesem speziellen Beispiel einen Mehrkernprozessor ausnutzen, bei dem jede Iteration vom Ergebnis der vorherigen abhängt?

BEARBEITEN 2

Die Benchmark-Tests von Daniel Fisher und Ilmari Karonen ließen mich denken, dass das Problem mit der bestimmten Version von gcc (4.7.2) und vielleicht mit einem bestimmten Build davon (Arch x86_64 Linux) zusammenhängt, den ich auf meinen Computern verwende. Also wiederholte ich den Test auf der intel core i7 Kiste mit debian x86_64 Linux, gcc version 4.4.5 und ifort version 12.1.0

$ gfortran -O3 -o ftest ftest.f90
$ time ./ftest
  0.16184945593939115     

real    0m0.272s
user    0m0.268s
sys     0m0.004s

und

$ ifort -O3 -o ftest ftest.f90
$ time ./ftest
  -0.211417093282753     

real    0m0.178s
user    0m0.176s
sys     0m0.004s

Für mich ist dies ein sehr akzeptabler Leistungsunterschied, der mich niemals dazu bringen würde, diese Frage zu stellen. Es scheint, dass ich in Arch Linux-Foren nach diesem Problem fragen muss.

Die Erklärung der ganzen Geschichte ist aber trotzdem sehr willkommen.

  • Warum ist der berechnete Wert unterschiedlich?

    – Ausruhen

    14. Dezember 2012 um 11:56 Uhr

  • Es ist näher an x8, haben Sie eine Maschine mit 8 Kernen? Auto-Vektorisierung sollte auch eine Rolle spielen, aber das haben Sie deaktiviert. Das Benchmarking von Code ohne Einschalten des Optimierers ist Zeitverschwendung.

    – Hans Passant

    14. Dezember 2012 um 12:04 Uhr

  • +1: Mit Optimierung kompilieren oder weggehen.

    – ams

    14. Dezember 2012 um 12:29 Uhr

  • Der Grund, warum die Optimierung wichtig gewesen sein könnte, ist, dass gcc bei der Optimierung manchmal Bibliotheksaufrufe durch andere Dinge (entweder andere Bibliotheksaufrufe oder Inline-Code) ersetzt.

    – Wels_Man

    14. Dezember 2012 um 19:20 Uhr

  • @Digikata, ifort führt keinerlei Schleifenentrollung durch. Es übersetzt die Schleife sehr wörtlich und ruft in seine eigene optimierte auf cos Routine, die es irgendwie schafft, eine schnellere Cosinus-Berechnung durchzuführen als fcos. Auf den ersten Blick das zerlegte cos Die Routine sieht aus wie die Verwendung einer tabellarischen Argumentreduktion. GCC ruft die systemweite Mathematikbibliothek auf, die etwas Ähnliches implementiert, aber nicht so optimiert für Intel-Prozessoren wie libimf tut.

    – Christo Iljew

    14. Dezember 2012 um 23:06 Uhr


Das meiste davon ist auf Unterschiede in der Mathematikbibliothek zurückzuführen. Einige zu beachtende Punkte:

  • Ja, die x86-Prozessoren mit der x87-Einheit haben fsin- und fcos-Anweisungen. Sie sind jedoch in Mikrocode implementiert, und es gibt keinen besonderen Grund, warum sie schneller sein müssen als eine reine Softwareimplementierung.
  • GCC hat keine eigene Mathematikbibliothek, sondern nutzt die vom System bereitgestellte. Unter Linux wird dies normalerweise von glibc bereitgestellt.
  • 32-Bit-x86-glibc verwendet fsin/fcos.
  • x86_64 glibc verwendet Softwareimplementierungen, die die SSE2-Einheit verwenden. Lange Zeit war dies eine viel langsamer als die 32-Bit-glibc-Version, die nur die x87-Anweisungen verwendet hat. Es wurden jedoch (etwas vor kurzem) Verbesserungen vorgenommen, so dass die Situation je nachdem, welche Glibc-Version Sie haben, möglicherweise nicht mehr so ​​​​schlecht ist wie früher.
  • Die Intel-Compiler-Suite ist mit einer SEHR schnellen Mathematikbibliothek (libimf) gesegnet. Darüber hinaus enthält es vektorisierte transzendentale mathematische Funktionen, die Schleifen mit diesen Funktionen oft weiter beschleunigen können.

  • fsin und fcos sind schneller als Implementierungen in C, aber sie sind ziemlich ungenau da sie auf einem falschen Wert von pi basieren (sic!).

    – fuz

    13. Dezember 2015 um 18:29 Uhr

  • @fuz: Es ist nicht gerade ein falsch Wert, aber es ist “nur” 80-Bit long double Präzision. (64-Bit-Signifikand, und die nächsten 2 Ziffern des genauen Werts sind zufällig 0 also 66-Bit-Signifikand). fsin Die Bereichsreduzierung verwendet Pi korrekt gerundet auf 80 Bit long double. Aber ja, das ist nicht gut genug in der Nähe +-Pi wo es zu einer katastrophalen Stornierung führt, aber mit Software besser zu werden, erfordert eine erweiterte Präzision. (z.B double double Ich vermute)

    – Peter Cordes

    6. August 2019 um 1:40 Uhr


  • Auch verwandt: Aufrufen der fsincos-Anweisung in LLVM langsamer als Aufrufen von libc sin/cos-Funktionen? zeigt einen Fall, in dem Software-Mathematikbibliotheken schneller sind als fsincosnicht langsamer.

    – Peter Cordes

    6. August 2019 um 1:47 Uhr


1373150cookie-checkWarum ist die gcc-Mathematikbibliothek so ineffizient?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy