Ersetzen der außerordentlich langsamen Funktion pow()

Lesezeit: 5 Minuten

Benutzer-Avatar
tpg2114

Wir haben einen CFD-Löser und beim Ausführen einer Simulation wurde festgestellt, dass er auf einigen Computern außerordentlich langsam lief, auf anderen jedoch nicht. Bei Verwendung von Intel VTune wurde festgestellt, dass die folgende Zeile das Problem war (in Fortran):

RHOV= RHO_INF*((1.0_wp - COEFF*EXP(F0)))**(1.0_wp/(GAMM - 1.0_wp))

Beim Einbohren mit VTune wurde das Problem zurückverfolgt call pow Fließband und beim Verfolgen des Stapels zeigte es, dass es verwendet wurde __slowpow(). Nach einigem Suchen, diese Seite tauchte auf und beschwerte sich über dasselbe.

Auf der Maschine mit der libc-Version 2.12 dauerte die Simulation 18 Sekunden. Auf der Maschine mit der libc-Version 2.14 dauerte die Simulation 0 Sekunden.

Basierend auf den Informationen auf der oben genannten Seite tritt das Problem auf, wenn die Basis zu pow() liegt nahe bei 1,0. Also haben wir einen weiteren einfachen Test durchgeführt, bei dem wir die Basis um eine beliebige Zahl vor dem skaliert haben pow() und dann durch die Zahl geteilt, die nach dem zum Exponenten erhoben wird pow() Anruf. Dadurch sank auch bei der libc 2.12 die Laufzeit von 18 Sekunden auf 0 Sekunden.

Es ist jedoch unpraktisch, dies überall im Code zu platzieren, wo wir es tun a**b. Wie würde man vorgehen, um die zu ersetzen pow() Funktion in libc? Ich hätte zum Beispiel gerne das Fließband call pow vom Fortran-Compiler generiert, um eine benutzerdefinierte Datei aufzurufen pow() Funktion, die wir schreiben, die die Skalierung durchführt, ruft die libc auf pow() und dividiert dann durch die Skalierung. Wie erstellt man eine für den Compiler transparente Zwischenschicht?

Bearbeiten

Zur Verdeutlichung suchen wir nach etwas wie (Pseudo-Code):

double pow(a,b) {
   a *= 5.0
   tmp = pow_from_libc(a,b)
   return tmp/pow_from_libc(5.0, b)
}

Kann man die laden pow aus libc und benennen Sie es in unserer benutzerdefinierten Funktion um, um Namenskonflikte zu vermeiden? Wenn die customPow.o Datei umbenennen könnte pow von libc, was passiert, wenn libc noch für andere Dinge benötigt wird? Würde das einen Namenskonflikt zwischen verursachen pow in customPow.o und pow im libc?

  • Guter alter Fortran! Interessante Frage aber +1

    – Austin Henley

    14. Februar 2012 um 5:38 Uhr

Benutzer-Avatar
Jonathan Dursi

Nun, warte jetzt. Die Bibliothek ruft nicht an __slowpow() nur um mit dir zu spielen; es ruft __slowpow() weil es glaubt, dass die zusätzliche Genauigkeit notwendig ist, um ein genaues Ergebnis für die Werte zu liefern, die Sie ihm geben (in diesem Fall Basis sehr nahe 1, Exponent der Ordnung 1). Wenn Ihnen die Genauigkeit dieser Berechnung wichtig ist, sollten Sie verstehen, warum das so ist und ob es wichtig ist, bevor Sie versuchen, es zu umgehen. Es könnte der Fall sein, dass für (sagen wir) große negative F0 diese ganze Sache sicher auf 1 gerundet werden kann; oder auch nicht, je nachdem, was später mit diesem Wert gemacht wird. Wenn Sie jemals 1.d0 minus dieses Ergebnis benötigen, werden Sie diese zusätzliche Genauigkeit benötigen.

  • Das stimmt sicherlich. Aber zumindest in unserem Fall ist unser Code dimensional, sodass wir nur dann eine Basis in der Nähe haben, wenn wir Dinge für die Visualisierung oder Nachbearbeitung berechnen, also ist es nicht besonders wichtig, innerhalb von 1e-15 an die richtige Antwort zu kommen . Ich habe einen Vergleich durchgeführt, um zu sehen, wie viel wir verlieren, und der Fehler ist ~1e-13, was für unseren genauen Code zweiter Ordnung sowieso kleiner ist als unser Diskretisierungsfehler, sodass wir alle pow() sicher durch einen etwas weniger genauen ersetzen können .

    – tpg2114

    14. Februar 2012 um 21:43 Uhr

Schreiben Sie einfach Ihre eigene pow Funktion, setzen Sie die .o Datei in einem statischen Bibliotheksarchiv libmypow.a irgendwo im Bibliothekspfad des Linkers und übergeben -lmypow beim Verlinken.

  • Würde das der Brauch zulassen pow Funktion zum Aufrufen pow aber in der libc? Dieser Brauch pow skaliert bei Bedarf einfach die Basis und ruft dann libc auf pow und entskalieren Sie dann bei Bedarf. Es scheint, als würde es einige Namenskonflikte geben.

    – tpg2114

    14. Februar 2012 um 5:40 Uhr

  • Wenn Sie dynamische Verknüpfungen verwenden, gibt es dlsym Hacks, die Sie verwenden können, um das gewünschte Verhalten zu erreichen, aber es ist zerbrechlich. Ein besserer Ansatz, wenn Sie ihn nur benötigen, um auf Systemen mit dem GNU-Linker zu arbeiten, ist der --wrap Option zu ld (die gcc übergehen kann ld über -Wl,--wrap,pow). Sie setzen dann __wrap_pow in libmypow.aund rufen Sie an __real_pow wo es die libc pow verwenden muss, und alles sollte gut sein.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    14. Februar 2012 um 6:06 Uhr

pow(a,b) ist das gleiche wie exp(b*ln(a))vielleicht funktioniert dieser Ersatz für Sie.

  • Das würde wahrscheinlich die Langsamkeit des Aufrufs umgehen, aber wir suchen nach einer Möglichkeit, den von der generierten Funktionsaufruf im Wesentlichen zu ersetzen ** Betreiber in Fortran, ohne die tatsächliche Codebasis, die wir haben, ändern zu müssen, wenn möglich.

    – tpg2114

    14. Februar 2012 um 5:35 Uhr

  • Verknüpfen Sie also Ihre eigene Version von pow(), die diese Identität verwendet.

    – Hans Passant

    14. Februar 2012 um 8:07 Uhr

  • dies ergibt ein anderes Ergebnis für 1,0000000000000020^1,5: 1,0000000000000031 mit call pow1,0000000000000029 mit -ffast-math und 1,5000000000000013 mit exp(b*ln(a))

    – Steabert

    14. Februar 2012 um 10:06 Uhr

Ich habe dies selbst getestet, und zwar wenn ich das Testprogramm von der von Ihnen verlinkten Seite kompiliere, verwende ich es call pow im Assemblercode. Kompilieren jedoch mit Optimierung -ffast-math Es gibt keinen Call to Pow, aber das Ergebnis ist etwas anders.

1357660cookie-checkErsetzen der außerordentlich langsamen Funktion pow()

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

Privacy policy