Jaki jest najszybszy sposób obliczenia razem grzechu i cos?

100

Chciałbym obliczyć zarówno sinus, jak i współsinus wartości razem (na przykład, aby utworzyć macierz rotacji). Oczywiście mógłbym je obliczyć osobno, jeden po drugim, npa = cos(x); b = sin(x); , ale zastanawiam się, czy istnieje szybszy sposób, gdy potrzebuję obu wartości.

Edycja: podsumowanie dotychczasowych odpowiedzi:

  • Vlad powiedział, że istnieje polecenie asmFSINCOSobliczające oba z nich (prawie w tym samym czasie, co wywołanieFSINsamego)

  • Jak zauważył Chi , ta optymalizacja jest czasami już wykonywana przez kompilator (przy użyciu flag optymalizacji).

  • caf wskazał, że działasincosisincosfprawdopodobnie są dostępne i można je wywołać bezpośrednio, po prostu włączającmath.h

  • Podejście Tanasciusa do korzystania z tabeli przeglądowej jest dyskutowane jako kontrowersyjne. (Jednak na moim komputerze iw scenariuszu porównawczym działa 3x szybciej niżsincosz prawie taką samą dokładnością dla 32-bitowych liczb zmiennoprzecinkowych).

  • Joel Goodwin połączył się z ciekawym podejściem do ekstremalnie szybkiej techniki przybliżania z całkiem dobrą dokładnością (dla mnie jest to nawet szybsze niż przeglądanie tabeli)

Danvil
źródło
1
Zobacz także to pytanie dotyczące natywnej implementacji sin / cos: stackoverflow.com/questions/1640595
Joel Goodwin
1
spróbuj sinx ~ x-x^3/6i cosx~1-x^2/4jako przybliżenia, jeśli bardziej zależy Ci na szybkości niż dokładności. Możesz dodawać terminy w obu seriach, kładąc większy nacisk na dokładność ( en.wikipedia.org/wiki/Taylor_series przewiń w dół do serii tryg taylor). Zauważ, że jest to ogólny sposób przybliżenia dowolnej funkcji, która ma różne nczasy. Więc jeśli masz jakąś większą funkcję, do której należą te sinus i cosinus, uzyskasz znacznie większe przyspieszenie, jeśli przybliżasz ją zamiast sinusa, cos niezależnie.
ldog
To zła technika z bardzo niską dokładnością. Zobacz post Joela Goodwina. Seria Taylora została zamieszczona poniżej. Napisz to jako odpowiedź.
Danvil
1
Cóż, zależy to od twoich wymagań, jeśli chcesz dokładności Szeregi Taylora będą dobrym przybliżeniem tylko wtedy, gdy potrzebujesz wartości xbliskich pewnego punktu x_0, a następnie rozszerz serię Taylora wokół x_0zamiast 0. To da doskonałą dokładność w pobliżu, x_0ale im dalej gorzej wyniki. Prawdopodobnie pomyślałeś, że dokładność jest do niczego, gdy spojrzałeś na podaną odpowiedź i wypróbowałeś ją dla wartości dalekich od 0. Odpowiedź brzmi: sin, cos rozszerzony wokół 0.
ldog

Odpowiedzi:

52

Nowoczesne procesory Intel / AMD mają instrukcje FSINCOS do jednoczesnego obliczania funkcji sinus i cosinus. Jeśli potrzebujesz silnej optymalizacji, być może powinieneś jej użyć.

Oto mały przykład: http://home.broadpark.no/~alein/fsincos.html

Oto kolejny przykład (dla MSVC): http://www.codeguru.com/forum/showthread.php?t=328669

Oto kolejny przykład (z gcc): http://www.allegro.cc/forums/thread/588470

Mam nadzieję, że jeden z nich pomoże. (Sam nie skorzystałem z tej instrukcji, przepraszam.)

Ponieważ są obsługiwane na poziomie procesora, spodziewam się, że będą znacznie szybsze niż przeszukiwanie tabeli.

Edycja:
Wikipedia to sugerujeFSINCOS została dodana na 387 procesorach, więc trudno jest znaleźć procesor, który go nie obsługuje.

Edycja:
dokumentacja Intela stwierdza, że FSINCOSjest to tylko około 5 razy wolniejsze niż FDIV(tj. Dzielenie zmiennoprzecinkowe).

Edycja:
Należy pamiętać, że nie wszystkie współczesne kompilatory optymalizują obliczenia sinusa i cosinusa w wywołaniu funkcjiFSINCOS . W szczególności mój VS 2008 nie robił tego w ten sposób.

Edycja:
pierwszy link do przykładu jest martwy, ale nadal istnieje wersja w Wayback Machine .

Vlad
źródło
1
@phkahler: Byłoby świetnie. Nie wiem, czy taka optymalizacja jest stosowana przez współczesne kompilatory.
Vlad
12
fsincosInstrukcja jest nie „dość szybko”. Według własnego podręcznika optymalizacji firmy Intel wymaga to od 119 do 250 cykli na najnowszych mikroarchitekturach. Biblioteka Intel Math (rozprowadzany z MTK), dla porównania, można oddzielnie obliczyć sini cosmniej niż 100 cykli, przy użyciu implementacji oprogramowania, który używa SSE zamiast jednostki x87. Podobna implementacja oprogramowania, która oblicza oba jednocześnie, mogłaby być jeszcze szybsza.
Stephen Canon
2
@Vlad: Biblioteki matematyczne ICC nie są open-source i nie mam licencji na ich redystrybucję, więc nie mogę opublikować zestawu. Mogę ci jednak powiedzieć, że nie ma wbudowanych sinobliczeń, z których mogliby skorzystać; używają tych samych instrukcji SSE, co wszyscy inni. Odnośnie twojego drugiego komentarza, prędkość względem fdivjest nieistotna; jeśli istnieją dwa sposoby zrobienia czegoś, a jeden jest dwa razy szybszy od drugiego, nie ma sensu nazywać wolniejszego „szybkim”, niezależnie od tego, ile czasu zajmuje to w stosunku do jakiegoś zupełnie niezwiązanego zadania.
Stephen Canon
1
Funkcja oprogramowania sinw ich bibliotece zapewnia pełną podwójną precyzję. fsincosInstrukcja zapewnia nieco większą dokładność (dwukrotnie przedłużony), ale dodatkowo dokładność zostaje wyrzucone w większości programów, które nazywamy sinfunkcję, a jego wynik jest zazwyczaj zaokrągla się do podwójnej precyzji przez później operacji arytmetycznych lub są zapisywane w pamięci. W większości sytuacji zapewniają taką samą dokładność w praktycznym zastosowaniu.
Stephen Canon
4
Należy również zauważyć, że fsincosnie jest to pełna implementacja sama w sobie; potrzebujesz dodatkowego kroku redukcji zakresu, aby umieścić argument w poprawnym zakresie wejściowym dla fsincosinstrukcji. Biblioteka sini cosfunkcje obejmują tę redukcję, a także podstawowe obliczenia, więc są one jeszcze szybsze (w porównaniu) niż wskazywane przeze mnie czasy cykli.
Stephen Canon
39

Nowoczesne procesory x86 mają instrukcję fsincos, która zrobi dokładnie to, o co prosisz - oblicza sin i cos w tym samym czasie. Dobry kompilator optymalizujący powinien wykryć kod, który oblicza sin i cos dla tej samej wartości i użyć polecenia fsincos, aby to wykonać.

Aby to zadziałało, trzeba było trochę zmienić flagi kompilatora, ale:

$ 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, używa instrukcji fsincos!

Chi
źródło
To jest fajne! Czy mógłbyś wyjaśnić, co robi -mfpmath = 387? Czy działa również z MSVC?
Danvil,
1
Zauważ to -ffast-mathi -mfpmathw niektórych przypadkach prowadzi do różnych wyników.
Dębilski
3
mfpmath = 387 zmusi gcc do użycia instrukcji x87 zamiast instrukcji SSE. Podejrzewam, że MSVC ma podobne optymalizacje i flagi, ale nie mam pod ręką MSVC, aby mieć pewność. Korzystanie z instrukcji x87 prawdopodobnie będzie miało negatywny wpływ na wydajność w innym kodzie, ale powinieneś również spojrzeć na moją drugą odpowiedź, aby użyć MKL Intela.
Chi,
Mój stary gcc 3.4.4 z cygwin generuje 2 oddzielne wywołania do fsini fcos. :-(
Vlad
Wypróbowano z programem Visual Studio 2008 z włączoną najwyższą optymalizacją. Wywołuje 2 funkcje biblioteczne __CIsini __CIcos.
Vlad
13

Kiedy potrzebujesz wydajności, możesz użyć wstępnie obliczonej tabeli sin / cos (wystarczy jedna tabela, przechowywana jako słownik). Cóż, zależy to od potrzebnej dokładności (być może stół byłby za duży), ale powinien być naprawdę szybki.

tanascius
źródło
Następnie wartość wejściową należy zmapować na [0,2 * pi] (lub mniejszą z dodatkowymi kontrolami), a to wywołanie fmod zjada wydajność. W mojej (prawdopodobnie nieoptymalnej) implementacji nie mogłem uzyskać wydajności z tabelą przeglądową. Czy masz jakąś radę?
Danvil
11
Wstępnie obliczona tabela będzie prawie na pewno wolniejsza niż zwykłe wywoływanie, sinponieważ wstępnie obliczona tabela usunie pamięć podręczną.
Andreas Brinck
1
To zależy od wielkości stołu. Tabela z 256 wpisami jest często dość dokładna i zużywa tylko 1Kb ... jeśli używasz jej dużo, czy nie utknie w pamięci podręcznej bez negatywnego wpływu na resztę wydajności aplikacji?
Mr. Boy
@Danvil: Oto przykład sinusowej tabeli wyszukiwania en.wikipedia.org/wiki/Lookup_table#Computing_sines . Jednak zakłada, że ​​już zmapowałeś swoje dane wejściowe na [0; 2pi].
tanascius
@AndreasBrinck Nie posunąłbym się tak daleko. To zależy (TM). Nowoczesne pamięci podręczne są ogromne, a tabele wyszukiwania małe. Dość często, jeśli zadbasz trochę o układ pamięci, twoja tablica przeglądowa nie musi mieć żadnego wpływu na wykorzystanie pamięci podręcznej przez resztę twoich obliczeń. Fakt, że tabela wyszukiwania mieści się w pamięci podręcznej, jest jednym z powodów, dla których jest tak szybki. Nawet w Javie, gdzie trudno jest precyzyjnie kontrolować układ memów, miałem olbrzymią wydajność dzięki tabelom przeglądowym.
Jarrod Smith
13

Technicznie rzecz biorąc, osiągnąłbyś to używając liczb zespolonych i wzoru Eulera . Tak więc coś w stylu (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();

powinien dać ci sinus i cosinus w jednym kroku. Jak to się robi wewnętrznie, jest kwestią używanego kompilatora i biblioteki. Zrobienie tego w ten sposób mogłoby (i mogłoby) zająć więcej czasu (tylko dlatego, że formuła Eulera jest głównie używana do obliczania złożonego expprzy użyciu sini cos- a nie na odwrót), ale może być możliwa pewna teoretyczna optymalizacja.


Edytować

Nagłówki w <complex>GNU C ++ 4.2 używają jawnych obliczeń sini coswewnątrz polar, więc nie wygląda to zbyt dobrze do optymalizacji, chyba że kompilator zrobi trochę magii (zobacz przełączniki -ffast-mathi -mfpmathtak, jak napisano w odpowiedzi Chi ).

Dębilski
źródło
przepraszam, ale wzór Eulera tak naprawdę nie mówi ci, jak coś obliczyć, jest to tylko tożsamość (choć bardzo użyteczna), która wiąże złożone wykładniki z prawdziwymi funkcjami trygonometrycznymi. Istnieją korzyści z obliczania razem sinusa i cosinusa, ale obejmują one wspólne wyrażenia podrzędne, a twoja odpowiedź nie omawia tego.
Jason S
12

Możesz obliczyć jedną z nich, a następnie użyć tożsamości:

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

ale jak mówi @tanascius, najlepszym rozwiązaniem jest tabela obliczona wcześniej.

Mitch Wheat
źródło
8
I pamiętaj, że użycie tej metody wymaga obliczenia mocy i pierwiastka kwadratowego, więc jeśli ważna jest wydajność, upewnij się, że jest to faktycznie szybsze niż bezpośrednie obliczanie drugiej funkcji trygonometrycznej.
Tyler McHenry
4
sqrt()jest często zoptymalizowany sprzętowo, więc może być wtedy bardzo szybszy sin()lub cos(). Moc jest po prostu rozmnażaniem się, więc nie używaj pow(). Istnieje kilka sztuczek, które pozwalają szybko uzyskać dość dokładne pierwiastki kwadratowe bez wsparcia sprzętowego. Na koniec pamiętaj, aby utworzyć profil, zanim to zrobisz.
deft_code Kwietnia
12
Zauważ, że √ (1 - cos ^ 2 x) jest mniej dokładne niż bezpośrednie obliczenie sin x, w szczególności gdy x ~ 0.
kennytm
1
Dla małego x bardzo ładna jest seria Taylora dla y = sqrt (1-x * x). Możesz uzyskać dobrą dokładność za pomocą pierwszych 3 wyrażeń i wymaga to tylko kilku mnożeń i jednej zmiany. Użyłem go w kodzie punktu stałego.
phkahler
1
@phkahler: Twoja seria Taylora nie ma zastosowania, ponieważ kiedy x ~ 0, cos x ~ 1.
kennytm
10

Jeśli używasz biblioteki GNU C, możesz:

#define _GNU_SOURCE
#include <math.h>

a dostaniesz oświadczeń o sincos(), sincosf()i sincosl()funkcje, które obliczają obie wartości razem - przypuszczalnie w najszybszy sposób do architektury docelowej.

kawiarnia
źródło
8

Na tej stronie forum jest bardzo interesująca rzecz, która koncentruje się na wyszukiwaniu dobrych i szybkich przybliżeń: http://www.devmaster.net/forums/showthread.php?t=5784

Zastrzeżenie: Sam nie używałem żadnych z tych rzeczy.

Aktualizacja z 22 lutego 2018 r .: Wayback Machine to jedyny sposób, aby teraz odwiedzić oryginalną stronę: https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/fast-and-accurate- sinus-cosinus

Joel Goodwin
źródło
Spróbowałem również tego i dał mi całkiem niezłe wyniki. Ale sin i cos są obliczane niezależnie.
Danvil,
Mam wrażenie, że to obliczenie sinusa / cosinusa będzie szybsze niż uzyskanie sinusa i użycie przybliżenia pierwiastka kwadratowego, aby uzyskać cosinus, ale test to zweryfikuje. Podstawowa zależność między sinusem i cosinusem jest zależna od fazy; czy można zakodować, aby móc ponownie wykorzystać wartości sinusoidalne, które obliczasz dla wywołań cosinusowych z przesunięciem fazowym, biorąc to pod uwagę? (To może być naciągane, ale musiałem zapytać)
Joel Goodwin
Nie bezpośrednio (pomimo pytania dokładnie o to). Potrzebuję sin i cos o wartości x i nie ma sposobu, aby wiedzieć, czy w innym miejscu przypadkowo obliczyłem x + pi / 2 ...
Danvil
Użyłem go w swojej grze do narysowania koła cząstek. Ponieważ jest to tylko efekt wizualny, wynik jest wystarczająco bliski, a wydajność jest naprawdę imponująca.
Maxim Kamalov
Nie jestem pod wrażeniem; Przybliżenia Czebyszewa zwykle dają największą dokładność dla danego wykonania.
Jason S
7

Wiele bibliotek matematycznych języka C, jak wskazuje caf, ma już sincos (). Godnym uwagi wyjątkiem jest MSVC.

  • Sun ma sincos () od co najmniej 1987 (dwadzieścia trzy lata; mam papierową stronę podręcznika)
  • HPUX 11 miał go w 1997 roku (ale nie ma go w HPUX 10.20)
  • Dodano do glibc w wersji 2.1 (luty 1999)
  • Stał się wbudowany w gcc 3.4 (2004), __builtin_sincos ().

A jeśli chodzi o wyszukiwanie, Eric S. Raymond w Art of Unix Programming (2004) (rozdział 12) mówi wyraźnie, że to zły pomysł (w chwili obecnej):

„Innym przykładem jest wstępne obliczanie małych tabel - na przykład tabela sin (x) na stopień do optymalizacji obrotów w silniku grafiki 3D zajmie 365 × 4 bajty na nowoczesnej maszynie. Zanim procesory osiągną wystarczająco dużo szybkości niż pamięć, aby wymagać buforowania była to oczywista optymalizacja szybkości. Obecnie może być szybsze przeliczanie za każdym razem, niż płacenie za procent dodatkowych błędów pamięci podręcznej spowodowanych przez tabelę.

„Ale w przyszłości sytuacja może się powtórzyć, gdy pamięci podręczne będą się powiększać. Ogólnie rzecz biorąc, wiele optymalizacji ma charakter tymczasowy i może łatwo przekształcić się w pesymizację, gdy zmieniają się wskaźniki kosztów. Jedynym sposobem na poznanie jest mierzenie i oglądanie”. (z Art of Unix Programming )

Ale sądząc po powyższej dyskusji, nie wszyscy się zgadzają.

Joseph Quinsey
źródło
10
„365 x 4 bajty”. Musisz wziąć pod uwagę lata przestępne, więc powinno to faktycznie wynosić 365,25 x 4 bajty. A może miał zamiar użyć liczby stopni w okręgu zamiast liczby dni w roku ziemskim.
Ponkadoodle
@Wallacoloo: Niezła obserwacja. Brakowało mi tego. Ale błąd jest w oryginale .
Joseph Quinsey
LOL. Dodatkowo zaniedbuje fakt, że w wielu grach komputerowych z tego obszaru będziesz potrzebować tylko skończonej liczby kątów. Nie ma wtedy żadnych chybień w pamięci podręcznej, jeśli znasz możliwe kąty. Użyłbym tabel dokładnie w tym przypadku i dał fsincos(instrukcje CPU!) Innym. Często jest tak szybkie, jak interpolacja sinusa i cos z dużego stołu.
Erich Schubert,
5

Nie wierzę, że tabele przeglądowe są koniecznie dobrym pomysłem na ten problem. O ile wymagania dotyczące dokładności nie są bardzo niskie, stół musi być bardzo duży. Nowoczesne procesory mogą wykonywać wiele obliczeń, podczas gdy wartość jest pobierana z pamięci głównej. Nie jest to jedno z tych pytań, na które można właściwie odpowiedzieć argumentacją (nawet moją), przetestować, zmierzyć i rozważyć dane.

Spojrzałbym jednak na szybkie implementacje SinCos, które można znaleźć w bibliotekach, takich jak ACML AMD i MKL Intela.

Znak wysokiej wydajności
źródło
3

Jeśli chcesz korzystać z produktu komercyjnego i obliczasz jednocześnie kilka obliczeń sin / cos (abyś mógł używać funkcji wektorowych), powinieneś sprawdzić bibliotekę jąder matematycznych firmy Intel.

Posiada funkcję SinCos

Zgodnie z tą dokumentacją uśrednia on 13,08 zegarów / element na rdzeniu 2 duo w trybie wysokiej dokładności, co, jak sądzę, będzie nawet szybsze niż fsincos.

Chi
źródło
1
Podobnie na OSX można użyć vvsincoslub vvsincosfz Accelerate.framework. Uważam, że AMD ma również podobne funkcje w swojej bibliotece wektorowej.
Stephen Canon
2

Gdy wydajność ma kluczowe znaczenie dla tego rodzaju rzeczy, wprowadzenie tabeli przeglądowej nie jest niczym niezwykłym.

Tom Cabanski
źródło
2

Jeśli chodzi o kreatywne podejście, co powiesz na rozszerzenie serii Taylor? Ponieważ mają podobne terminy, możesz zrobić coś podobnego do następującego pseudo:

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
}

Oznacza to, że robisz coś takiego: zaczynając od x i 1 dla sin i cosinus, postępuj zgodnie ze wzorem - odejmij x ^ 2/2! od cosinusa odejmij x ^ 3/3! od sinusa dodaj x ^ 4/4! do cosinusa dodaj x ^ 5/5! sine ...

Nie mam pojęcia, czy to byłoby skuteczne. Jeśli potrzebujesz mniejszej precyzji niż dają ci wbudowane funkcje sin () i cos (), może to być opcja.

Tesserex
źródło
W rzeczywistości współczynnik rozszerzenia i-sinus wynosi x / i razy i-współczynnik rozszerzenia cosinus. Ale
wątpiłbym,
1
Czebyszewa jest znacznie lepszy niż Taylor w aproksymacji funkcji wielomianu. Nie używaj przybliżenia Taylora.
Timmmm
Jest tu sporo liczbowych faux pas; licznik i mianownik szybko stają się duże, co prowadzi do błędów zmiennoprzecinkowych. Nie wspominając już o tym, jak decydujesz, czym jest „niewystarczająca precyzja” i jak ją obliczyć? Przybliżenie Taylora jest dobre w sąsiedztwie jednego punktu; z dala od tego punktu szybko stają się niedokładne i wymagają dużej liczby terminów, dlatego sugestia Timmmma dotycząca przybliżenia Czebyszewa (która tworzy dobre przybliżenia w danym przedziale) jest dobra.
Jason S
2

W bibliotece CEPHES jest fajne rozwiązanie, które może być dość szybkie i możesz dodawać / usuwać dokładność dość elastycznie, aby uzyskać nieco więcej / mniej czasu procesora.

Pamiętaj, że cos (x) i sin (x) to rzeczywiste i urojone części exp (ix). Chcemy więc obliczyć exp (ix), aby uzyskać oba. Obliczamy wstępnie exp (iy) dla niektórych dyskretnych wartości y między 0 a 2pi. Przesuwamy x do przedziału [0, 2pi). Następnie wybieramy y, które jest najbliższe x i piszemy
exp (ix) = exp (iy + (ix-iy)) = exp (iy) exp (i (xy)).

Otrzymujemy exp (iy) z tabeli przeglądowej. A ponieważ | xy | jest mała (co najwyżej połowa odległości między wartościami y), szereg Taylora będzie się dobrze zbiegał w zaledwie kilku terminach, więc używamy tego dla exp (i (xy)). A potem potrzebujemy złożonego mnożenia, aby otrzymać exp (ix).

Inną fajną właściwością tego jest to, że możesz go wektoryzować za pomocą SSE.

Jsl
źródło
2

Możesz zajrzeć na http://gruntthepeon.free.fr/ssemath/ , który oferuje wektoryzowaną implementację SSE inspirowaną biblioteką CEPHES. Ma dobrą dokładność (maksymalne odchylenie od sin / cos rzędu 5e-8) i prędkość (nieznacznie przewyższa fsincos na podstawie pojedynczego wywołania i wyraźny zwycięzca w wielu wartościach).

SleuthEye
źródło
1

Opublikowałem tutaj rozwiązanie obejmujące montaż inline ARM zdolny do obliczania zarówno sinusa, jak i cosinusa dwóch kątów naraz: Szybki sinus / cosinus dla ARMv7 + NEON

jcayzac
źródło
0

Czy myślałeś o zadeklarowaniu tabel przeglądowych dla tych dwóch funkcji? Nadal musiałbyś „obliczyć” sin (x) i cos (x), ale byłoby to zdecydowanie szybsze, gdybyś nie potrzebował wysokiego stopnia dokładności.

Frank Shearar
źródło
0

Kompilator MSVC może używać (wewnętrznych) funkcji SSE2

 ___libm_sse2_sincos_ (for x86)
 __libm_sse2_sincos_  (for x64)

w zoptymalizowanych kompilacjach, jeśli określono odpowiednie flagi kompilatora (co najmniej / O2 / arch: SSE2 / fp: fast). Nazwy tych funkcji zdają się sugerować, że nie obliczają one oddzielnych wartości sin i cos, ale obie „w jednym kroku”.

Na przykład:

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

Montaż (dla x86) z / fp: szybki:

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 (dla x86) bez / fp: fast, ale z / fp: precyzyjne zamiast tego (co jest domyślne) wywołuje oddzielne sin i cos:

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

So / fp: fast jest obowiązkowe dla optymalizacji sincos.

Ale proszę o tym pamiętać

___libm_sse2_sincos_

może nie jest tak dokładny jak

__libm_sse2_sin_precise
__libm_sse2_cos_precise

ze względu na brak słowa „precyzyjne” na końcu jego nazwy.

Na moim "nieco" starszym systemie (Intel Core 2 Duo E6750) z najnowszym kompilatorem MSVC 2019 i odpowiednimi optymalizacjami mój test porównawczy pokazuje, że wywołanie sincos jest około 2,4 razy szybsze niż oddzielne wywołania sin i cos.

xy
źródło