Was ist der schnellste Weg, um Sünde und Cos zusammen zu berechnen?

100

Ich möchte sowohl den Sinus als auch den Co-Sinus eines Wertes zusammen berechnen (zum Beispiel um eine Rotationsmatrix zu erstellen). Natürlich könnte ich sie wie nacheinander separat berechnen a = cos(x); b = sin(x);, aber ich frage mich, ob es einen schnelleren Weg gibt, wenn beide Werte benötigt werden.

Bearbeiten: Um die bisherigen Antworten zusammenzufassen:

  • Vlad sagte, dass es den Befehl asm gibt, derFSINCOSbeide berechnet (fast zur gleichen Zeit wie ein Anruf anFSINallein).

  • Wie Chi bemerkt hat, wird diese Optimierung manchmal bereits vom Compiler durchgeführt (bei Verwendung von Optimierungsflags).

  • caf wies darauf hin, dass funktionensincosundsincosfwahrscheinlich verfügbar sind und direkt durch einfaches einschließen aufgerufen werden könnenmath.h

  • Der Tanascius- Ansatz zur Verwendung einer Nachschlagetabelle wird kontrovers diskutiert. (Auf meinem Computer und in einem Benchmark-Szenario läuft es jedoch dreimal schneller alssincosmit fast der gleichen Genauigkeit für 32-Bit-Gleitkommazahlen.)

  • Joel Goodwin verband sich mit einem interessanten Ansatz einer extrem schnellen Approximationstechnik mit ziemlich guter Genauigkeit (für mich ist dies sogar schneller als das Nachschlagen der Tabelle).

Danvil
quelle
1
Siehe auch diese Frage zur nativen Implementierung von sin / cos: stackoverflow.com/questions/1640595
Joel Goodwin
1
versuchen sinx ~ x-x^3/6und cosx~1-x^2/4als Annäherungen , wenn Sie kümmern sich um Geschwindigkeit mehr als Genauigkeit. Sie können Begriffe in beiden Serien hinzufügen, wenn Sie der Genauigkeit mehr Gewicht beimessen ( en.wikipedia.org/wiki/Taylor_series scrollen Sie nach unten, um Taylor-Serien auszulösen.) Beachten Sie, dass dies eine allgemeine Methode ist, um jede gewünschte Funktion zu approximieren, die unterschiedliche nZeiten aufweist. Wenn Sie also eine größere Funktion haben, zu der Sinus und Cosinus gehören, werden Sie eine viel größere Geschwindigkeit erhalten, wenn Sie sie anstelle der Sünde approximieren, cos unabhängig.
ldog
Dies ist eine schlechte Technik mit sehr schlechter Genauigkeit. Siehe Beitrag von Joel Goodwin. Taylor-Serien wurden unten veröffentlicht. Bitte poste es als Antwort.
Danvil
1
Nun, es hängt von Ihren Anforderungen ab. Wenn Sie Genauigkeit wünschen, ist die Taylor-Reihe nur dann eine gute Annäherung, wenn Sie Werte xnahe an einem bestimmten Punkt benötigen. x_0Erweitern Sie dann Ihre Taylor-Reihe um x_0statt 0. Dies gibt Ihnen eine hervorragende Genauigkeit in der Nähe, x_0aber je weiter Sie entfernt sind Je schlechter die Ergebnisse. Sie haben wahrscheinlich gedacht, dass die Genauigkeit schlecht ist, als Sie sich die gegebene Antwort angesehen und sie für Werte ausprobiert haben, die weit davon entfernt sind 0. Diese Antwort ist mit Sünde, weil um 0 erweitert.
ldog

Antworten:

52

Moderne Intel / AMD-Prozessoren verfügen über Anweisungen FSINCOSzur gleichzeitigen Berechnung von Sinus- und Cosinusfunktionen. Wenn Sie eine starke Optimierung benötigen, sollten Sie diese möglicherweise verwenden.

Hier ist ein kleines Beispiel: http://home.broadpark.no/~alein/fsincos.html

Hier ist ein weiteres Beispiel (für MSVC): http://www.codeguru.com/forum/showthread.php?t=328669

Hier ist noch ein weiteres Beispiel (mit gcc): http://www.allegro.cc/forums/thread/588470

Hoffe einer von ihnen hilft. (Ich habe diese Anweisung leider nicht selbst verwendet.)

Da sie auf Prozessorebene unterstützt werden, erwarte ich, dass sie viel schneller sind als Tabellensuchen.

Bearbeiten:
Wikipedia schlägt vor, dass FSINCOSbei 387 Prozessoren hinzugefügt wurde, so dass Sie kaum einen Prozessor finden können, der dies nicht unterstützt.

Bearbeiten:
Intels Dokumentation besagt, dass dies FSINCOSnur etwa fünfmal langsamer ist als FDIV(dh Gleitkommadivision).

Bearbeiten:
Bitte beachten Sie, dass nicht alle modernen Compiler die Berechnung von Sinus und Cosinus in einen Aufruf von optimieren FSINCOS. Insbesondere mein VS 2008 hat es nicht so gemacht.

Bearbeiten:
Der erste Beispiellink ist tot, aber es gibt noch eine Version auf der Wayback-Maschine .

Vlad
quelle
1
@phkahler: Das wäre toll. Ich weiß nicht, ob eine solche Optimierung von den modernen Compilern verwendet wird.
Vlad
12
Die fsincosAnweisung ist nicht "ziemlich schnell". In Intels eigenem Optimierungshandbuch wird angegeben, dass bei neueren Mikroarchitekturen zwischen 119 und 250 Zyklen erforderlich sind. Die mit ICC vertriebene Mathematikbibliothek von Intel kann im Vergleich dazu separatsin und cosin weniger als 100 Zyklen mithilfe einer Softwareimplementierung berechnet werden , die SSE anstelle der x87-Einheit verwendet. Eine ähnliche Software-Implementierung, die beide gleichzeitig berechnet, könnte noch schneller sein.
Stephen Canon
2
@Vlad: Die ICC-Mathematikbibliotheken sind nicht Open Source und ich habe keine Lizenz, um sie weiterzugeben, daher kann ich die Assembly nicht veröffentlichen. Ich kann Ihnen jedoch sagen, dass es keine integrierte sinBerechnung gibt, die sie nutzen könnten. Sie verwenden dieselben SSE-Anweisungen wie alle anderen. Für Ihren zweiten Kommentar ist die Geschwindigkeit relativ zu fdivunerheblich. Wenn es zwei Möglichkeiten gibt, etwas zu tun, und eine doppelt so schnell ist wie die andere, ist es nicht sinnvoll, die langsamere als "schnell" zu bezeichnen, unabhängig davon, wie lange es im Vergleich zu einer völlig unabhängigen Aufgabe dauert.
Stephen Canon
1
Die Softwarefunktion sinin ihrer Bibliothek bietet volle Genauigkeit mit doppelter Genauigkeit. Der fsincosBefehl liefert etwas mehr Genauigkeit (doppelt erweitert), aber diese zusätzliche Genauigkeit wird in den meisten Programmen, die die sinFunktion aufrufen , weggeworfen , da das Ergebnis normalerweise durch spätere arithmetische Operationen oder durch Speichern im Speicher auf doppelte Genauigkeit gerundet wird. In den meisten Situationen liefern sie für den praktischen Gebrauch die gleiche Genauigkeit.
Stephen Canon
4
Beachten Sie auch, dass dies fsincoskeine vollständige Implementierung für sich ist. Sie benötigen einen zusätzlichen Schritt zur Bereichsreduzierung, um das Argument in den gültigen Eingabebereich für die fsincosAnweisung zu setzen. Die Bibliothek sinund die cosFunktionen umfassen diese Reduzierung sowie die Kernberechnung, sodass sie (im Vergleich) noch schneller sind als die von mir aufgelisteten Zykluszeiten.
Stephen Canon
39

Moderne x86-Prozessoren verfügen über eine fsincos-Anweisung, die genau das tut, was Sie verlangen - berechnen Sie gleichzeitig sin und cos. Ein guter Optimierungscompiler sollte Code erkennen, der sin und cos für denselben Wert berechnet, und den Befehl fsincos verwenden, um dies auszuführen.

Es dauerte ein wenig, bis die Compiler-Flags funktionierten, aber:

$ gcc --version
i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5488)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cat main.c
#include <math.h> 

struct Sin_cos {double sin; double cos;};

struct Sin_cos fsincos(double val) {
  struct Sin_cos r;
  r.sin = sin(val);
  r.cos = cos(val);
  return r;
}

$ gcc -c -S -O3 -ffast-math -mfpmath=387 main.c -o main.s

$ cat main.s
    .text
    .align 4,0x90
.globl _fsincos
_fsincos:
    pushl   %ebp
    movl    %esp, %ebp
    fldl    12(%ebp)
    fsincos
    movl    8(%ebp), %eax
    fstpl   8(%eax)
    fstpl   (%eax)
    leave
    ret $4
    .subsections_via_symbols

Tada, es benutzt die Anweisung fsincos!

Chi
quelle
Das ist cool! Können Sie erklären, was -mfpmath = 387 tut? Und funktioniert es auch mit MSVC?
Danvil
1
Beachten Sie dies -ffast-mathund -mfpmathführen in einigen Fällen zu unterschiedlichen Ergebnissen.
Debilski
3
mfpmath = 387 zwingt gcc, x87-Anweisungen anstelle von SSE-Anweisungen zu verwenden. Ich vermute, dass MSVC ähnliche Optimierungen und Flags hat, aber ich habe MSVC nicht zur Hand, um sicher zu sein. Die Verwendung von x87-Anweisungen wird wahrscheinlich die Leistung in anderem Code beeinträchtigen. Sie sollten sich jedoch auch meine andere Antwort ansehen, um Intels MKL zu verwenden.
Chi
Mein alter gcc 3.4.4 von cygwin erzeugt 2 separate Aufrufe von fsinund fcos. :-(
Vlad
Versucht mit Visual Studio 2008 mit höchsten aktivierten Optimierungen. Es ruft 2 Bibliotheksfunktionen auf __CIsinund __CIcos.
Vlad
13

Wenn Sie Leistung benötigen, können Sie eine vorberechnete sin / cos-Tabelle verwenden (eine Tabelle reicht aus, die als Wörterbuch gespeichert ist). Nun, es hängt von der Genauigkeit ab, die Sie benötigen (vielleicht wäre der Tisch zu groß), aber es sollte wirklich schnell gehen.

Tanascius
quelle
Dann muss der Eingabewert auf [0,2 * pi] abgebildet werden (oder kleiner mit zusätzlichen Überprüfungen), und dieser Aufruf von fmod beeinträchtigt die Leistung. In meiner (wahrscheinlich suboptimalen) Implementierung konnte ich mit der Nachschlagetabelle keine Leistung erzielen. Würdest du hier einen Rat haben?
Danvil
11
Eine vorberechnete Tabelle ist mit ziemlicher Sicherheit langsamer als nur ein Aufruf, sinda die vorberechnete Tabelle den Cache in den Papierkorb legt.
Andreas Brinck
1
Es kommt darauf an, wie groß der Tisch ist. Eine Tabelle mit 256 Einträgen ist oft recht genau und benötigt nur 1 KB. Wenn Sie sie häufig verwenden, bleibt sie dann nicht im Cache hängen, ohne die Leistung der restlichen App zu beeinträchtigen.
Mr. Boy
@ Danvil: Hier ist ein Beispiel für eine Sinus-Nachschlagetabelle en.wikipedia.org/wiki/Lookup_table#Computing_sines . Es wird jedoch davon ausgegangen, dass Sie Ihre Eingabe auch bereits [0; 2pi] zugeordnet haben.
Tanascius
@AndreasBrinck Ich würde nicht so weit gehen. Es kommt darauf an (TM). Moderne Caches sind riesig und Nachschlagetabellen sind klein. Sehr oft muss Ihre Nachschlagetabelle keinen Einfluss auf die Cache-Auslastung des Restes Ihrer Berechnung haben, wenn Sie beim Speicherlayout etwas Sorgfalt walten lassen. Die Tatsache, dass die Nachschlagetabelle in den Cache passt, ist einer der Gründe, warum sie so schnell ist. Selbst in Java, wo es schwierig ist, das Mem-Layout genau zu steuern, habe ich mit Nachschlagetabellen massive Leistungsgewinne erzielt.
Jarrod Smith
13

Technisch gesehen würden Sie dies erreichen, indem Sie komplexe Zahlen und die Euler-Formel verwenden . Also so etwas wie (C ++)

complex<double> res = exp(complex<double>(0, x));
// or equivalent
complex<double> res = polar<double>(1, x);
double sin_x = res.imag();
double cos_x = res.real();

sollte Ihnen Sinus und Cosinus in einem Schritt geben. Wie dies intern erfolgt, hängt vom verwendeten Compiler und der verwendeten Bibliothek ab. Es könnte (und könnte) durchaus länger dauern, dies auf diese Weise zu tun (nur weil Eulers Formel hauptsächlich zur Berechnung des Komplexes expmit sinund verwendet wird cos- und nicht umgekehrt), aber möglicherweise ist eine theoretische Optimierung möglich.


Bearbeiten

Die Header in <complex>GNU C ++ 4.2 verwenden explizite Berechnungen von sinund cosinnerhalb polar, sodass es für Optimierungen dort nicht allzu gut aussieht, es sei denn, der Compiler zaubert etwas (siehe -ffast-mathund -mfpmathwechselt, wie in Chis Antwort geschrieben ).

Debilski
quelle
Entschuldigung, aber Eulers Formel sagt Ihnen eigentlich nicht, wie man etwas berechnet, es ist nur eine Identität (wenn auch eine sehr nützliche), die komplexe Exponentiale mit realen trigonometrischen Funktionen in Beziehung setzt. Es gibt Vorteile, Sinus und Cosinus zusammen zu berechnen, aber sie beinhalten häufige Unterausdrücke, und Ihre Antwort diskutiert dies nicht.
Jason S
12

Sie können entweder berechnen und dann die Identität verwenden:

cos (x) 2 = 1 - sin (x) 2

Aber wie @tanascius sagt, ist eine vorberechnete Tabelle der richtige Weg.

Mitch Wheat
quelle
8
Beachten Sie außerdem, dass bei dieser Methode eine Leistung und eine Quadratwurzel berechnet werden. Wenn also die Leistung wichtig ist, stellen Sie sicher, dass dies tatsächlich schneller ist als die direkte Berechnung der anderen Triggerfunktion.
Tyler McHenry
4
sqrt()wird oft in Hardware optimiert, so dass es sehr wohl schneller sein kann als sin()oder cos(). Die Kraft ist nur Selbstmultiplikation, also nicht verwenden pow(). Es gibt einige Tricks, um ohne Hardware-Unterstützung sehr schnell ziemlich genaue Quadratwurzeln zu erhalten. Stellen Sie zum Schluss sicher, dass Sie ein Profil haben, bevor Sie dies tun.
Deft_code
12
Beachten Sie, dass √ (1 - cos ^ 2 x) weniger genau ist als die direkte Berechnung von sin x, insbesondere wenn x ~ 0.
kennytm
1
Für kleines x ist die Taylor-Reihe für y = sqrt (1-x * x) sehr schön. Mit den ersten drei Begriffen können Sie eine gute Genauigkeit erzielen, und es sind nur wenige Multiplikationen und eine Schicht erforderlich. Ich habe es im Festkomma-Code verwendet.
Phkahler
1
@phkahler: Ihre Taylor-Serie gilt nicht, weil wenn x ~ 0, cos x ~ 1.
kennytm
10

Wenn Sie die GNU C-Bibliothek verwenden, können Sie Folgendes tun:

#define _GNU_SOURCE
#include <math.h>

und Sie erhalten Erklärungen der sincos(), sincosf()und sincosl()Funktionen , die beide Werte zusammen berechnen - vermutlich auf dem schnellsten Weg für Ihre Zielarchitektur.

caf
quelle
8

Es gibt sehr interessante Dinge auf dieser Forenseite, die sich darauf konzentrieren, gute Annäherungen zu finden, die schnell sind: http://www.devmaster.net/forums/showthread.php?t=5784

Haftungsausschluss: Ich habe nichts davon selbst verwendet.

Update 22. Februar 2018: Wayback Machine ist die einzige Möglichkeit, die Originalseite jetzt zu besuchen: https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/fast-and-accurate- Sinus-Cosinus

Joel Goodwin
quelle
Ich habe es auch versucht und es hat mir eine ziemlich gute Leistung gebracht. Aber sin und cos werden unabhängig voneinander berechnet.
Danvil
Mein Gefühl ist, dass diese Sinus / Cosinus-Berechnung schneller ist als Sinus zu erhalten und eine Quadratwurzel-Näherung zu verwenden, um Cosinus zu erhalten, aber ein Test wird dies bestätigen. Die primäre Beziehung zwischen Sinus und Cosinus ist eine der Phasen; Ist es möglich zu codieren, damit Sie die Sinuswerte, die Sie für die phasenverschobenen Cosinusaufrufe berechnen, wiederverwenden können, indem Sie dies berücksichtigen? (Dies kann eine Strecke sein, musste aber fragen)
Joel Goodwin
Nicht direkt (trotz der Frage, die genau dies stellt). Ich brauche sin und cos mit einem Wert x und es gibt keine Möglichkeit zu wissen, ob ich an einem anderen Ort zufällig x + pi / 2 berechnet habe ...
Danvil
Ich habe es in meinem Spiel verwendet, um einen Kreis von Partikeln zu zeichnen. Da es sich nur um einen visuellen Effekt handelt, ist das Ergebnis nah genug und die Leistung ist wirklich beeindruckend.
Maxim
Ich bin nicht beeindruckt; Chebyshev-Näherungen geben Ihnen normalerweise die höchste Genauigkeit für eine bestimmte Leistung.
Jason S
7

Viele C-Mathe-Bibliotheken haben, wie das Café angibt, bereits sincos (). Die bemerkenswerte Ausnahme ist MSVC.

  • Sun hat seit mindestens 1987 sincos () (dreiundzwanzig Jahre; ich habe eine gedruckte Manpage)
  • HPUX 11 hatte es 1997 (aber nicht in HPUX 10.20)
  • Hinzugefügt zu glibc in Version 2.1 (Feb 1999)
  • Wurde ein eingebautes in gcc 3.4 (2004), __builtin_sincos ().

Und in Bezug auf das Nachschlagen sagt Eric S. Raymond in der Kunst der Unix-Programmierung (2004) (Kapitel 12) ausdrücklich, dass dies eine schlechte Idee ist (zum gegenwärtigen Zeitpunkt):

"Ein weiteres Beispiel ist die Vorberechnung kleiner Tabellen. Beispielsweise benötigt eine Tabelle mit sin (x) nach Grad zur Optimierung der Rotationen in einer 3D-Grafik-Engine auf einem modernen Computer 365 × 4 Byte. Bevor Prozessoren schneller als Speicher waren, um Caching zu fordern Dies war eine offensichtliche Geschwindigkeitsoptimierung. Heutzutage ist es möglicherweise schneller, jedes Mal neu zu berechnen, als den Prozentsatz der durch die Tabelle verursachten zusätzlichen Cache-Fehler zu bezahlen.

"In Zukunft könnte sich dies jedoch wieder ändern, wenn die Caches größer werden. Im Allgemeinen sind viele Optimierungen nur vorübergehend und können sich leicht in Pessimierungen verwandeln, wenn sich die Kostenverhältnisse ändern. Der einzige Weg zu wissen ist zu messen und zu sehen." (aus der Kunst der Unix-Programmierung )

Nach der obigen Diskussion sind sich jedoch nicht alle einig.

Joseph Quinsey
quelle
10
"365 x 4 Bytes". Sie müssen Schaltjahre berücksichtigen, damit diese tatsächlich 365,25 x 4 Byte betragen. Oder vielleicht wollte er die Anzahl der Grad in einem Kreis anstelle der Anzahl der Tage in einem Erdjahr verwenden.
Ponkadoodle
@ Wallacoloo: Schöne Beobachtung. Ich habe es verpasst. Aber der Fehler liegt im Original .
Joseph Quinsey
LOL. Außerdem vernachlässigt er die Tatsache, dass Sie in vielen Computerspielen dieses Bereichs nur eine begrenzte Anzahl von Winkeln benötigen. Es gibt dann keine Cache-Fehler, wenn Sie die möglichen Winkel kennen. Ich würde genau in diesem Fall Tabellen verwenden und fsincos(CPU-Anweisung!) Einen Versuch für die anderen geben. Es ist oft so schnell wie das Interpolieren von sin und cos von einem großen Tisch.
Erich Schubert
5

Ich glaube nicht, dass Nachschlagetabellen unbedingt eine gute Idee für dieses Problem sind. Sofern Ihre Genauigkeitsanforderungen nicht sehr niedrig sind, muss die Tabelle sehr groß sein. Und moderne CPUs können viel rechnen, während ein Wert aus dem Hauptspeicher abgerufen wird. Dies ist keine dieser Fragen, die durch Argumente (nicht einmal meine) richtig beantwortet, getestet und gemessen und die Daten berücksichtigt werden können.

Aber ich würde mich auf die schnellen Implementierungen von SinCos konzentrieren, die Sie in Bibliotheken wie AMDs ACML und Intels MKL finden.

Hochleistungsmarke
quelle
3

Wenn Sie bereit sind, ein kommerzielles Produkt zu verwenden und gleichzeitig eine Reihe von Sin / Cos-Berechnungen berechnen (damit Sie Vektorfunktionen verwenden können), sollten Sie sich die Math Kernel Library von Intel ansehen .

Es hat eine Sincos-Funktion

Laut dieser Dokumentation sind es durchschnittlich 13,08 Takte / Element auf Core 2 Duo im hochgenauen Modus, was meiner Meinung nach sogar noch schneller sein wird als fsincos.

Chi
quelle
1
Ebenso kann man unter OSX vvsincosoder vvsincosfaus dem Accelerate.framework verwenden. Ich glaube, dass AMD ähnliche Funktionen auch in seiner Vektorbibliothek hat.
Stephen Canon
2

Wenn die Leistung für solche Dinge entscheidend ist, ist es nicht ungewöhnlich, eine Nachschlagetabelle einzuführen.

Tom Cabanski
quelle
2

Wie wäre es mit einer Erweiterung der Taylor-Serie für einen kreativen Ansatz? Da sie ähnliche Begriffe haben, können Sie so etwas wie das folgende Pseudo machen:

numerator = x
denominator = 1
sine = x
cosine = 1
op = -1
fact = 1

while (not enough precision) {
    fact++
    denominator *= fact
    numerator *= x

    cosine += op * numerator / denominator

    fact++
    denominator *= fact
    numerator *= x

    sine += op * numerator / denominator

    op *= -1
}

Dies bedeutet, dass Sie so etwas tun: Beginnen Sie bei x und 1 für sin und cosine und folgen Sie dem Muster - subtrahieren Sie x ^ 2/2! vom Kosinus subtrahiere x ^ 3/3! addiere vom Sinus x ^ 4/4! zum Cosinus addiere x ^ 5/5! zu sinus ...

Ich habe keine Ahnung, ob dies performant wäre. Wenn Sie weniger Präzision benötigen als die eingebauten sin () und cos (), kann dies eine Option sein.

Tesserex
quelle
Tatsächlich ist der i-Sinus-Verlängerungsfaktor das x / i-fache des i-Cosinus-Verlängerungsfaktors. Aber ich würde bezweifeln, dass die Verwendung der Taylor-Serie sehr schnell ist ...
Danvil
1
Chebyshev ist viel besser als Taylor für die Polynomfunktionsnäherung. Verwenden Sie keine Taylor-Näherung.
Timmmm
Es gibt hier eine Reihe von numerischen Fauxpas; Zähler und Nenner werden beide schnell groß und das führt zu Gleitkommafehlern. Ganz zu schweigen davon, wie Sie entscheiden, was "nicht genug Präzision" ist und wie Sie es berechnen? Die Taylor-Annäherung ist in der Nachbarschaft um einen einzelnen Punkt gut; Von diesem Punkt weg werden sie schnell ungenau und erfordern eine große Anzahl von Begriffen, weshalb Timmmms Vorschlag zur Chebyshev-Näherung (die über ein bestimmtes Intervall gute Näherungen erzeugt) gut ist.
Jason S
2

Es gibt eine nette Lösung in der CEPHES-Bibliothek, die ziemlich schnell sein kann und Sie können die Genauigkeit für etwas mehr / weniger CPU-Zeit ziemlich flexibel hinzufügen / entfernen.

Denken Sie daran, dass cos (x) und sin (x) die Real- und Imaginärteile von exp (ix) sind. Wir wollen also exp (ix) berechnen, um beides zu erhalten. Wir berechnen exp (iy) für einige diskrete Werte von y zwischen 0 und 2 pi vor. Wir verschieben x in das Intervall [0, 2pi]. Dann wählen wir das y aus, das x am nächsten liegt, und schreiben
exp (ix) = exp (iy + (ix-iy)) = exp (iy) exp (i (xy)).

Wir erhalten exp (iy) aus der Nachschlagetabelle. Und seit | xy | Ist die Taylor-Reihe klein (höchstens die Hälfte des Abstandes zwischen den y-Werten), konvergiert sie in wenigen Begriffen gut, daher verwenden wir sie für exp (i (xy)). Und dann brauchen wir nur eine komplexe Multiplikation, um exp (ix) zu erhalten.

Eine weitere nette Eigenschaft ist, dass Sie es mit SSE vektorisieren können.

Jsl
quelle
2

Vielleicht möchten Sie einen Blick auf http://gruntthepeon.free.fr/ssemath/ werfen , das eine vektorisierte SSE-Implementierung bietet, die von der CEPHES-Bibliothek inspiriert ist. Es hat eine gute Genauigkeit (maximale Abweichung von sin / cos in der Größenordnung von 5e-8) und Geschwindigkeit (übertrifft fsincos bei einem einzelnen Anruf leicht und ist ein klarer Gewinner über mehrere Werte).

SleuthEye
quelle
1

Ich habe hier eine Lösung veröffentlicht, die eine Inline-ARM-Baugruppe umfasst, mit der sowohl der Sinus als auch der Cosinus von zwei Winkeln gleichzeitig berechnet werden können: Schneller Sinus / Cosinus für ARMv7 + NEON

jcayzac
quelle
0

Haben Sie darüber nachgedacht, Nachschlagetabellen für die beiden Funktionen zu deklarieren? Sie müssten noch sin (x) und cos (x) "berechnen", aber es wäre deutlich schneller, wenn Sie kein hohes Maß an Genauigkeit benötigen.

Frank Shearar
quelle
0

Der MSVC-Compiler kann die (internen) SSE2-Funktionen verwenden

 ___libm_sse2_sincos_ (for x86)
 __libm_sse2_sincos_  (for x64)

in optimierten Builds, wenn entsprechende Compiler-Flags angegeben sind (mindestens / O2 / arch: SSE2 / fp: fast). Die Namen dieser Funktionen scheinen zu implizieren, dass sie nicht getrennte Sünde und cos berechnen, sondern beide "in einem Schritt".

Beispielsweise:

void sincos(double const x, double & s, double & c)
{
  s = std::sin(x);
  c = std::cos(x);
}

Assembly (für x86) mit / fp: schnell:

movsd   xmm0, QWORD PTR _x$[esp-4]
call    ___libm_sse2_sincos_
mov     eax, DWORD PTR _s$[esp-4]
movsd   QWORD PTR [eax], xmm0
mov     eax, DWORD PTR _c$[esp-4]
shufpd  xmm0, xmm0, 1
movsd   QWORD PTR [eax], xmm0
ret     0

Assembly (für x86) ohne / fp: schnell, aber mit / fp: präzise (was die Standardeinstellung ist) ruft separate sin und cos auf:

movsd   xmm0, QWORD PTR _x$[esp-4]
call    __libm_sse2_sin_precise
mov     eax, DWORD PTR _s$[esp-4]
movsd   QWORD PTR [eax], xmm0
movsd   xmm0, QWORD PTR _x$[esp-4]
call    __libm_sse2_cos_precise
mov     eax, DWORD PTR _c$[esp-4]
movsd   QWORD PTR [eax], xmm0
ret     0

Also / fp: schnell ist für die sincos-Optimierung obligatorisch.

Aber bitte beachten Sie das

___libm_sse2_sincos_

ist vielleicht nicht so genau wie

__libm_sse2_sin_precise
__libm_sse2_cos_precise

aufgrund des fehlenden "präzisen" am Ende seines Namens.

Auf meinem "etwas" älteren System (Intel Core 2 Duo E6750) mit dem neuesten MSVC 2019-Compiler und entsprechenden Optimierungen zeigt mein Benchmark, dass der Sincos-Aufruf etwa 2,4-mal schneller ist als separate Sin- und Cos-Aufrufe.

xy
quelle