2016-04-04 28 views
4

Haupt FrageFaster sin() für x64

Hat jemand eine schnelle Sünde für x64() Implementierung haben? Es muss nicht pures Pascal sein.

Erklärung

ich eine VCL-Anwendung haben, die viel langsamer in einigen Situationen läuft, wenn es für x64 kompiliert.

Es führt viele Gleitkomma-3D-Berechnungen durch, und ich habe dies auf die Tatsache zurückgeführt, dass System.Sin() und System.Cos() auf x64 viel langsamer sind, wenn die Eingabewerte groß werden.

Ich timed es habe durch eine einfache Test-Anwendung erstellen, die messen, wie lange es sin(x), mit unterschiedlichen Werten für x zu berechnen nimmt, und die Unterschiede sind riesig:

   call:  x64:  x86: 
       Sin(1) 16 ms 20 ms 
      Sin(10) 30 ms 20 ms 
      Sin(100) 32 ms 20 ms 
      Sin(1000) 34 ms 21 ms 
      Sin(10000) 30 ms 21 ms 
     Sin(100000) 30 ms 16 ms 
     Sin(1000000) 35 ms 20 ms 
     Sin(10000000) 581 ms 20 ms 
     Sin(100000000) 1026 ms 21 ms 
    Sin(1000000000) 1187 ms 22 ms 
    Sin(10000000000) 1320 ms 21 ms 
    Sin(100000000000) 1456 ms 20 ms 
    Sin(1000000000000) 1581 ms 17 ms 
Sin(10000000000000) 1717 ms 22 ms 
Sin(100000000000000) 1846 ms 23 ms 
      Sin(1E15) 1981 ms 21 ms 
      Sin(1E16) 2100 ms 21 ms 
      Sin(1E17) 2240 ms 22 ms 
      Sin(1E18) 2372 ms 18 ms 
       etc etc  etc 

Was Sie hier sehen, ist, dass sin(1E5) läuft etwa 300 mal so schnell wie sin(1E8).

Falls Sie interessiert sind, habe ich die obige Tabelle wie folgt erstellt haben:

{$APPTYPE CONSOLE} 
program SinTest; 

uses Diagnostics, Math, SysUtils; 

var 
    i : Integer; 
    x : double; 
    sw: TStopwatch; 

begin 
    x := 1; 

    while X < 1E18 do 
    begin 
    sw := TStopwatch.StartNew; 
    for i := 1 to 500000 do 
     System.Sin(x); 

    // WriteLn(System.sin(x), #9,System.Sin(fmod(x,2*pi))); 

    sw.Stop; 

    WriteLn(' ', ('Sin(' + round(x).ToString + ')'):20, ' ', sw.ElapsedMilliseconds,' ms'); 

    x := x * 10; 
    end; 

    WriteLn('Press any key to continue'); 
    readln; 
end. 

Hinweise:

  • Es gibt einige Fragen auf Stackoverflow bezüglich schneller Sinusfunktionen, aber keine von ihnen haben Quellcode, der nützlich ist, um zu Delphi zu portieren, wie dieser: Fastest implementation of sine, cosine and square root in C++ (doesn't need to be much accurate)

  • Der Rest des x64 läuft schneller als es ist 32bits cou nterpart

  • Ich habe ein bisschen beschissene Abhilfe gefunden, indem Sie dies tun: Sin(FMod(x,2*pi)). Es liefert die korrekten Ergebnisse und es läuft schnell für größere Zahlen. Bei kleineren Nummern ist es natürlich etwas langsamer.

+2

Vermutlich kümmert es Sie nicht um die Genauigkeit, oder Sie würden nicht trig Funktionen mit so großen Werten aufrufen. Sicherlich schätzen Sie, dass eine Abrundung bedeutet, dass trigonale Funktionen für solche Eingabewerte bedeutungslos sind? Oder ist Genauigkeit für Sie nicht wichtig? –

+1

Also, sehen Sie, wenn Sie die Ausgabe dieses Programms erraten können: '{$ APPTYPE CONSOLE} var s1, s2: Single; beginnen s1: = 10000000.5; s2: = 10000000.0; Writeln (s1 = s2); Ende. 'Hier ist ein Hinweis. Die Ausgabe ist nicht "FALSE". –

+1

Es scheint, dass MSVC es schneller machen kann, und ich wäre daran interessiert zu wissen, wie, denn ich wette, es macht es schneller für Eingabewerte, die auch sinnvoll sind. Aber für Ihre großen Eingabewerte verschwenden Sie Ihre Zeit mit dem Aufruf dieser Trigger-Funktionen, wie mein vorheriger Kommentar zeigt. –

Antwort

3

Zwar ist dies wohl eher stark im User-Modus Code entmutigt werden (und ist vollständig im Kernel-Modus-Code verboten), wenn Sie das Erbe x87 Verhalten in Ihrem x64-Code halten wollen Sie könnten eine Funktion wie folgt schreiben:

function SinX87(x:double):double; 
var 
    d : double; 
asm 
    movsd qword ptr [rbp+8], xmm0 
    fld qword ptr [rbp+8] 
    fsin 
    fstp qword ptr [rbp+8] 
    movsd xmm0, qword ptr [rbp+8] 
end; 

Dieses ein wenig Overhead hinzufügt, da Sie den Wert aus dem SSE registrieren auf den Stapel Pop haben, es in die x87-Einheit laden, um die Berechnung peform, Pop den Wert zurück auf den Stapel und dann laden Sie es zurück in XMM0 für die Funktion Ergebnis. Die sin Berechnung ist jedoch ziemlich schwer, also ist dies ein relativ geringer Overhead. Ich würde das nur wirklich tun, wenn Sie whatever idiosyncracies der x87 sin Implementierung beibehalten müssten.

Andere Bibliotheken existieren, die sin in x64-Code effizienter berechnen als die purepascal-Routinen von Delphi. Meine überwältigende Präferenz wäre hier, eine gute Menge von C++ - Routinen in eine DLL zu exportieren. Wie David sagte, ist die Verwendung von trigonometrischen Funktionen mit lächerlich großen Argumenten sowieso nicht sinnvoll.

+0

Cool, die Geschwindigkeit ist sehr stabil, egal welche Eingabe es bekommt. Für Werte kleiner als Pi ist es ein kleines bisschen langsamer; Der Rest ist immer schneller. Die Ergebnisse unterscheiden sich ein wenig von Delphis System.Sin(), aber für die Zahlen, mit denen ich arbeiten muss, ist es unbedeutend. Die Ergebnisse sehen gut aus. Genau das habe ich gebraucht. Jetzt muss ich nur noch hässliche {$ ifdef} Sachen hinzufügen, und die Leistung unter x64 wird wiederhergestellt. Vielen Dank! –

+0

@WoutervanNifterick Außerdem bin ich mir nicht sicher, wie Ausnahmen behandelt werden würden ... Ich würde es definitiv zuerst testen. Nicht sicher, ob das x87-Steuerwort im x64-Modus standardmäßig auf etwas Sinnvolles gesetzt wird - ich habe das schnell hochgeschraubt, aber es gibt Vorbehalte, auf die ich achten muss. –

+0

Getestet, und tatsächlich geht es etwas anders. Zum Beispiel wird 'SinX87 (NaN)' keine Ausnahme auslösen, wie es System.Sin() tut. Es gibt also tatsächlich Unterschiede, aber das ist eine große Hilfe. Ich werde einige zusätzliche Tests machen, aber bisher sieht es so aus, als ob es alles genau so macht, wie ich es brauche. –

2

Falls Sie in meine endgültige Lösung interessiert sind:

ich ein wenig experimentiert haben, um dies zu tun (wie LU RD und e). - Jerry Coffin vorgeschlagen):

function sin(x:double):double; 
begin 
    if x<1E6 then 
    Result := system.sin(x) 
    else 
    Result := system.sin(fmod(x,2*pi)); 
end; 

Vielleicht hat es etwas mit der Vorhersagbarkeit des Testcodes auf meiner speziellen CPU zu tun, aber kleinere Werte wurden tatsächlich schneller berechnet, wenn ich nicht die if, tat und nur immer Verwende fmod(). Seltsam, weil eine Teilung stattfinden muss, was ich erwarten würde, verlangsamt zu sein, als zwei Werte zu vergleichen.

Also das ist, was ich jetzt am Ende mit:

function sin(const x: double): double; { inline; } 
begin 
    {$IFDEF CPUX64} 
    Result := System.sin(Math.FMod(x,2*pi)); 
    {$ELSE} 
    Result := System.sin(x); 
    {$ENDIF} 
end; 

Durch die Art und Weise des Hinzufügen inline, lief es 1,5-mal schneller sogar. Es läuft dann genau so schnell wie J ... auf meinem Rechner. Aber auch ohne Inline ist das schon hunderte Male schneller als System.Sin(), darum gehe ich.

+1

Auch wenn Sie 'fmod (x, 2 * pi)' verwenden, wie @DavidHeffernan darauf hingewiesen hat, stoßen Sie gegen die Tatsache, dass 'x' als doppelt genaue Variable nicht mehr als etwa 17 Dezimalstellen an Informationen enthalten kann. so verlierst du all deine Genauigkeit von dem, was in die 'sin'-Funktion übertragen wird. Bsp .: Wenn Sie "x" von 100000000000000000,0 zu 100000000000000000.1, das einen 1-Radiantenschritt darstellt, "stechen", sind diese beiden Zahlen gleich, denn wenn die .1 hinzugefügt wird, ist sie verloren, weil die Variable mit doppelter Genauigkeit nicht breit genug ist halt die ganze Sache. Sie müssen einen anderen Weg finden, um 'x' zu codieren. –