Qual è il modo più veloce per calcolare peccato e cos insieme?


100

Vorrei calcolare insieme sia il seno che il co-seno di un valore (ad esempio per creare una matrice di rotazione). Ovviamente potrei calcolarli separatamente uno dopo l'altro come a = cos(x); b = sin(x);, ma mi chiedo se esista un modo più veloce quando sono necessari entrambi i valori.

Modifica: per riassumere le risposte finora:

  • Vlad ha detto che c'è il comando asmFSINCOSche li calcola entrambi (quasi nello stesso tempo di una chiamata aFSINsolo)

  • Come ha notato Chi , questa ottimizzazione a volte è già eseguita dal compilatore (quando si utilizzano i flag di ottimizzazione).

  • caf ha sottolineato che le funzionisincosesincosfsono probabilmente disponibili e possono essere chiamate direttamente includendo semplicementemath.h

  • L' approccio tanascius di utilizzare una tabella di ricerca è discusso controverso. (Tuttavia sul mio computer e in uno scenario di benchmark funziona 3 volte più velocemente rispettosincosa quasi la stessa precisione per i punti mobili a 32 bit.)

  • Joel Goodwin si è collegato a un approccio interessante di una tecnica di approssimazione estremamente veloce con una precisione abbastanza buona (per me, questo è ancora più veloce della ricerca nella tabella)


1
Vedi anche questa domanda sull'implementazione nativa di sin / cos: stackoverflow.com/questions/1640595
Joel Goodwin

1
prova sinx ~ x-x^3/6e cosx~1-x^2/4come approssimazioni se ti interessa la velocità più che la precisione. Puoi aggiungere termini in entrambe le serie man mano che dai più peso alla precisione ( en.wikipedia.org/wiki/Taylor_series scorri verso il basso fino a trig taylor series.) Nota che questo è un modo generale per approssimare qualsiasi funzione tu voglia che sia differenziata in ntempi. Quindi, se hai una funzione più grande a cui appartengono quel seno e quel coseno, otterrai una velocità molto maggiore se la approssimerai invece del peccato, cos è indipendentemente.
ldog

Questa è una tecnica scadente con una precisione molto scarsa. Vedi il post di Joel Goodwin. Le serie di Taylor sono state pubblicate di seguito. Per favore pubblicalo come risposta.
Danvil

1
Beh, dipende dalle tue esigenze, se vuoi la precisione la serie di Taylor sarà una buona approssimazione solo se hai bisogno di valori xvicini a un certo punto x_0, quindi espandi la tua serie di Taylor intorno x_0invece di 0. Questo ti darà un'eccellente precisione vicino x_0ma più lontano peggiorano i risultati. Probabilmente hai pensato che l'accuratezza facesse schifo perché hai guardato la risposta data e l'hai provata per valori lontani da 0. La risposta è con il peccato, che si è espanso intorno allo 0.
Cane

Risposte:


52

I moderni processori Intel / AMD hanno istruzioni FSINCOSper il calcolo simultaneo delle funzioni seno e coseno. Se hai bisogno di una forte ottimizzazione, forse dovresti usarlo.

Ecco un piccolo esempio: http://home.broadpark.no/~alein/fsincos.html

Ecco un altro esempio (per MSVC): http://www.codeguru.com/forum/showthread.php?t=328669

Ecco ancora un altro esempio (con gcc): http://www.allegro.cc/forums/thread/588470

Spero che uno di loro aiuti. (Non ho usato questa istruzione da solo, mi dispiace.)

Poiché sono supportati a livello di processore, mi aspetto che siano molto più veloci delle ricerche su tabella.

Modifica:
Wikipedia suggerisce che è FSINCOSstato aggiunto a 387 processori, quindi difficilmente puoi trovare un processore che non lo supporti.

Modifica:
la documentazione di Intel afferma che FSINCOSè circa 5 volte più lento di FDIV(cioè, divisione in virgola mobile).

Modifica: si
noti che non tutti i compilatori moderni ottimizzano il calcolo di seno e coseno in una chiamata a FSINCOS. In particolare, il mio VS 2008 non ha funzionato in questo modo.

Modifica:
il primo collegamento di esempio è morto, ma esiste ancora una versione su Wayback Machine .


1
@phkahler: sarebbe fantastico. Non so se tale ottimizzazione sia utilizzata dai compilatori moderni.
Vlad

12
L' fsincosistruzione non è "abbastanza veloce". Il manuale di ottimizzazione di Intel indica che richiede tra 119 e 250 cicli su micro-architetture recenti. La libreria matematica di Intel (distribuita con ICC), in confronto, può eseguire calcoli separatamentesin e cosin meno di 100 cicli, utilizzando un'implementazione software che utilizza SSE invece dell'unità x87. Un'implementazione software simile che calcola entrambi contemporaneamente potrebbe essere ancora più veloce.
Stephen Canon

2
@Vlad: Le librerie matematiche ICC non sono open-source e non ho una licenza per ridistribuirle, quindi non posso pubblicare l'assembly. Posso dirti che non ci sono sincalcoli incorporati da cui trarre vantaggio, tuttavia; usano le stesse istruzioni SSE di tutti gli altri. Secondo il tuo secondo commento, la velocità relativa a fdivè irrilevante; se ci sono due modi per fare qualcosa e uno è due volte più veloce dell'altro, non ha senso chiamare quello più lento "veloce", indipendentemente da quanto tempo ci vuole rispetto a un compito completamente non correlato.
Stephen Canon

1
La sinfunzione software nella loro libreria offre un'accuratezza a doppia precisione completa. L' fsincosistruzione fornisce un po 'più di precisione (doppia estensione), ma quella precisione extra viene eliminata nella maggior parte dei programmi che chiamano la sinfunzione, poiché il suo risultato viene solitamente arrotondato alla doppia precisione da operazioni aritmetiche successive o da un archivio in memoria. Nella maggior parte delle situazioni, offrono la stessa precisione per l'uso pratico.
Stephen Canon

4
Nota anche che fsincosnon è un'implementazione completa di per sé; è necessario un ulteriore passo di riduzione dell'intervallo per inserire l'argomento nell'intervallo di input valido per l' fsincosistruzione. La libreria sine le cosfunzioni includono questa riduzione così come il calcolo di base, quindi sono ancora più veloci (in confronto) rispetto ai tempi di ciclo che ho elencato potrebbero indicare.
Stephen Canon,

39

I moderni processori x86 hanno un'istruzione fsincos che farà esattamente quello che stai chiedendo: calcola sin e cos allo stesso tempo. Un buon compilatore ottimizzatore dovrebbe rilevare il codice che calcola seno e cos per lo stesso valore e utilizzare il comando fsincos per eseguirlo.

Ci sono voluti un po 'di tempo con i flag del compilatore perché funzionasse, ma:

$ 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, usa l'istruzione fsincos!


Questo è fantastico! Puoi spiegare cosa sta facendo -mfpmath = 387? E funziona anche con MSVC?
Danvil

1
Notalo -ffast-mathe -mfpmathin alcuni casi porta a risultati diversi.
Debilski

3
mfpmath = 387 costringerà gcc a usare le istruzioni x87 invece delle istruzioni SSE. Sospetto che MSVC abbia ottimizzazioni e flag simili, ma non ho MSVC a portata di mano per esserne sicuro. L'uso delle istruzioni x87 sarà probabilmente un danno alle prestazioni in altro codice, dovresti anche guardare la mia altra risposta, per usare MKL di Intel.
Chi,

Il mio vecchio gcc 3.4.4 di cygwin produce 2 chiamate separate a fsine fcos. :-(
Vlad

Provato con Visual Studio 2008 con le ottimizzazioni più elevate abilitate. Chiama 2 funzioni di libreria __CIsine __CIcos.
Vlad

13

Quando hai bisogno di prestazioni, puoi usare una tabella sin / cos precalcolata (una tabella andrà bene, memorizzata come dizionario). Beh, dipende dalla precisione di cui hai bisogno (forse la tabella sarebbe troppo grande), ma dovrebbe essere molto veloce.


Quindi il valore di input deve essere mappato a [0,2 * pi] (o inferiore con controlli aggiuntivi) e questa chiamata a fmod consuma le prestazioni. Nella mia implementazione (probabilmente non ottimale) non sono riuscito a ottenere prestazioni con la tabella di ricerca. Hai qualche consiglio qui?
Danvil

11
Una tabella precalcolata sarà quasi certamente più lenta della semplice chiamata sinperché la tabella precalcolata cestinerà la cache.
Andreas Brinck

1
Dipende da quanto è grande il tavolo. Una tabella a 256 voci è spesso abbastanza precisa e utilizza solo 1Kb ... se la usi molto, non si bloccherebbe nella cache senza influire negativamente sul resto delle prestazioni dell'app?
Mr. Boy

@ Danvil: ecco un esempio di una tabella di ricerca sinusoidale en.wikipedia.org/wiki/Lookup_table#Computing_sines . Tuttavia si presume che tu abbia già mappato anche il tuo input su [0; 2pi].
tanascius

@AndreasBrinck Non andrei così lontano. Dipende (TM). Le cache moderne sono enormi e le tabelle di ricerca sono piccole. Molto spesso, se presti un po 'di attenzione al layout della memoria, la tua tabella di ricerca non deve fare alcuna differenza per l'utilizzo della cache del resto del calcolo. Il fatto che la tabella di ricerca si adatti alla cache è uno dei motivi per cui è così veloce. Anche in Java, dove è difficile controllare con precisione il layout della memoria, ho ottenuto enormi vittorie in termini di prestazioni con le tabelle di ricerca.
Jarrod Smith

13

Tecnicamente, otterresti questo risultato utilizzando numeri complessi e la formula di Eulero . Quindi, qualcosa come (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();

dovrebbe darti seno e coseno in un unico passaggio. Il modo in cui questo viene fatto internamente è una questione del compilatore e della libreria utilizzati. Potrebbe (e potrebbe) impiegare più tempo per farlo in questo modo (solo perché la formula di Eulero è usata principalmente per calcolare il complesso expusando sine cos- e non il contrario) ma potrebbe essere possibile un'ottimizzazione teorica.


modificare

Le intestazioni <complex>per GNU C ++ 4.2 utilizzano calcoli espliciti di sine cosall'interno polar, quindi non sembra troppo buono per le ottimizzazioni a meno che il compilatore non faccia qualche magia (vedere le opzioni -ffast-mathe -mfpmathcome scritto nella risposta di Chi ).


scusa, ma la formula di Eulero in realtà non ti dice come calcolare qualcosa, è solo un'identità (anche se molto utile) che mette in relazione esponenziali complessi con funzioni trigonometriche reali. Ci sono vantaggi nel calcolare insieme seno e coseno, ma implicano sottoespressioni comuni e la tua risposta non ne discute.
Jason S

12

Potresti calcolare uno e poi usare l'identità:

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

ma come dice @tanascius, una tabella precalcolata è la strada da percorrere.


8
E tieni presente che l'utilizzo di questo metodo implica il calcolo di una potenza e una radice quadrata, quindi se le prestazioni sono importanti, assicurati di verificare che questo sia effettivamente più veloce del calcolo diretto dell'altra funzione trigonometrica.
Tyler McHenry

4
sqrt()è spesso ottimizzato nell'hardware, quindi potrebbe essere più veloce di sin()o cos(). Il potere è solo auto moltiplicazione, quindi non usarlo pow(). Ci sono alcuni trucchi per ottenere radici quadrate ragionevolmente accurate molto rapidamente senza supporto hardware. Infine, assicurati di creare un profilo prima di eseguire qualsiasi operazione.
deft_code

12
Notare che √ (1 - cos ^ 2 x) è meno accurato del calcolo diretto di sin x, in particolare quando x ~ 0.
kennytm

1
Per x piccola, la serie di Taylor per y = sqrt (1-x * x) è molto carina. Puoi ottenere una buona precisione con i primi 3 termini e richiede solo pochi moltiplicatori e un turno. L'ho usato in codice a virgola fissa.
phkahler

1
@phkahler: la tua serie Taylor non si applica perché quando x ~ 0, cos x ~ 1.
kennytm

10

Se usi la libreria GNU C, puoi fare:

#define _GNU_SOURCE
#include <math.h>

e si otterrà dichiarazioni dei sincos(), sincosf()e le sincosl()funzioni che calcolano entrambi i valori insieme - presumibilmente nel modo più veloce per la propria architettura di destinazione.


8

Ci sono cose molto interessanti in questa pagina del forum, che si concentra sulla ricerca di buone approssimazioni veloci: http://www.devmaster.net/forums/showthread.php?t=5784

Disclaimer: non ho usato niente di tutto questo da solo.

Aggiornamento 22 febbraio 2018: Wayback Machine è l'unico modo per visitare la pagina originale ora: https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/fast-and-accurate- seno-coseno


Ho provato anche questo e mi ha dato prestazioni abbastanza buone. Ma sin e cos vengono calcolati indipendentemente.
Danvil

La mia sensazione è che questo calcolo seno / coseno sarà più veloce di ottenere il seno e usare un'approssimazione della radice quadrata per ottenere il coseno, ma un test lo verificherà. La relazione primaria tra seno e coseno è una di fase; è possibile codificare in modo da poter riutilizzare i valori del seno calcolati per le chiamate del coseno sfasate tenendo conto di questo? (Potrebbe essere una forzatura, ma ho dovuto chiedere)
Joel Goodwin

Non direttamente (nonostante la domanda chieda esattamente questo). Ho bisogno di peccato e cos di un valore x e non c'è modo di sapere se in qualche altro posto ho calcolato casualmente x + pi / 2 ...
Danvil

L'ho usato nel mio gioco per disegnare un cerchio di particelle. Poiché è solo un effetto visivo, il risultato è abbastanza vicino e la performance è davvero impressionante.
Maxim Kamalov

Non sono impressionato; Le approssimazioni di Chebyshev di solito ti danno la massima precisione per una data prestazione.
Jason S

7

Molte librerie matematiche C, come indica caf, hanno già sincos (). L'eccezione degna di nota è MSVC.

  • Sun ha sincos () almeno dal 1987 (ventitré anni; ho una pagina di manuale cartacea)
  • HPUX 11 lo aveva nel 1997 (ma non è in HPUX 10.20)
  • Aggiunto a glibc nella versione 2.1 (febbraio 1999)
  • È diventato un built-in di gcc 3.4 (2004), __builtin_sincos ().

E per quanto riguarda la ricerca, Eric S. Raymond in The Art of Unix Programming (2004) (Capitolo 12) dice esplicitamente che questa è una cattiva idea (al momento presente):

"Un altro esempio è il pre-calcolo di piccole tabelle: ad esempio, una tabella di sin (x) per grado per l'ottimizzazione delle rotazioni in un motore grafico 3D richiederà 365 × 4 byte su una macchina moderna. Prima che i processori diventassero abbastanza più veloci della memoria da richiedere la memorizzazione nella cache , questa era un'ovvia ottimizzazione della velocità. Al giorno d'oggi potrebbe essere più veloce ricalcolare ogni volta piuttosto che pagare per la percentuale di cache mancati aggiuntivi causati dalla tabella.

"Ma in futuro, questo potrebbe cambiare di nuovo man mano che le cache aumentano. Più in generale, molte ottimizzazioni sono temporanee e possono facilmente trasformarsi in pessimizzazioni al variare dei rapporti di costo. L'unico modo per sapere è misurare e vedere". ( dall'arte della programmazione Unix )

Ma, a giudicare dalla discussione sopra, non tutti sono d'accordo.


10
"365 x 4 byte". È necessario tenere conto degli anni bisestili, quindi dovrebbero essere effettivamente 365,25 x 4 byte. O forse intendeva usare il numero di gradi in un cerchio invece del numero di giorni in un anno terrestre.
Ponkadoodle

@Wallacoloo: bella osservazione. Mi mancava. Ma l'errore è nell'originale .
Joseph Quinsey

LOL. Inoltre, trascura il fatto che in molti dei giochi per computer di quella zona, avrai solo bisogno di un numero finito di angoli. Non ci sono errori di cache quindi, se conosci i possibili angoli. Userei le tabelle esattamente in questo caso e fsincosproverei (istruzioni della CPU!) Per gli altri. Spesso è veloce quanto l'interpolazione di peccato e cos da un grande tavolo.
Erich Schubert

5

Non credo che le tabelle di ricerca siano necessariamente una buona idea per questo problema. A meno che i requisiti di precisione non siano molto bassi, la tabella deve essere molto grande. E le moderne CPU possono eseguire molti calcoli mentre un valore viene recuperato dalla memoria principale. Questa non è una di quelle domande a cui è possibile rispondere adeguatamente con argomenti (nemmeno i miei), testare, misurare e considerare i dati.

Ma guarderei alle implementazioni veloci di SinCos che trovi in ​​librerie come ACML di AMD e MKL di Intel.


3

Se sei disposto a utilizzare un prodotto commerciale e stai calcolando un numero di calcoli seno / coseno allo stesso tempo (in modo da poter utilizzare funzioni vettoriali), dovresti controllare la libreria Math Kernel di Intel.

Ha una funzione sincos

Secondo tale documentazione, ha una media di 13,08 clock / elemento su core 2 duo in modalità ad alta precisione, che penso sarà anche più veloce di fsincos.


1
Allo stesso modo, su OSX si può usare vvsincoso vvsincosfdal Accelerate.framework. Credo che AMD abbia funzioni simili anche nella loro libreria vettoriale.
Stephen Canon


2

Quando le prestazioni sono fondamentali per questo genere di cose, non è insolito introdurre una tabella di ricerca.


2

Per un approccio creativo, che ne dici di espandere la serie Taylor? Poiché hanno termini simili, potresti fare qualcosa di simile al seguente 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
}

Ciò significa che fai qualcosa del genere: iniziando da x e 1 per seno e coseno, segui lo schema - sottrai x ^ 2/2! dal coseno, sottrai x ^ 3/3! dal seno, aggiungi x ^ 4/4! al coseno, aggiungi x ^ 5/5! a seno ...

Non ho idea se questo sarebbe performante. Se hai bisogno di una precisione inferiore rispetto a sin () e cos (), potrebbe essere un'opzione.


In realtà il fattore di estensione i-seno è x / i volte il fattore di estensione i-coseno. Ma dubito che usare la serie Taylor sia davvero veloce ...
Danvil

1
Chebyshev è molto meglio di Taylor per l'approssimazione della funzione polinomiale. Non utilizzare l'approssimazione di Taylor.
Timmmm

Ci sono un sacco di passi falsi numerici qui; numeratore e denominatore diventano rapidamente grandi e questo porta a errori in virgola mobile. Per non parlare di come decidi cosa sia "precisione insufficiente" e come calcolarlo? L'approssimazione di Taylor è buona nell'intorno di un singolo punto; lontano da quel punto diventano rapidamente inaccurati e richiedono un gran numero di termini, motivo per cui il suggerimento di Timmmm sull'approssimazione di Chebyshev (che crea buone approssimazioni su un dato intervallo) è buono.
Jason S

2

C'è una bella soluzione nella libreria CEPHES che può essere abbastanza veloce e puoi aggiungere / rimuovere la precisione in modo abbastanza flessibile per un po 'più / meno tempo della CPU.

Ricorda che cos (x) e sin (x) sono le parti reale e immaginaria di exp (ix). Quindi vogliamo calcolare exp (ix) per ottenere entrambi. Precalcoliamo exp (iy) per alcuni valori discreti di y compresi tra 0 e 2 ppi. Spostiamo x all'intervallo [0, 2pi). Quindi selezioniamo la y più vicina a x e scriviamo
exp (ix) = exp (iy + (ix-iy)) = exp (iy) exp (i (xy)).

Otteniamo exp (iy) dalla tabella di ricerca. E poiché | xy | è piccola (al massimo la metà della distanza tra i valori y), la serie di Taylor converge bene in pochi termini, quindi la usiamo per exp (i (xy)). E poi abbiamo solo bisogno di una moltiplicazione complessa per ottenere exp (ix).

Un'altra bella proprietà di questo è che puoi vettorializzarlo usando SSE.


2

Potresti voler dare un'occhiata a http://gruntthepeon.free.fr/ssemath/ , che offre un'implementazione vettoriale SSE ispirata alla libreria CEPHES. Ha una buona precisione (deviazione massima da seno / cos nell'ordine di 5e-8) e velocità (supera leggermente fsincos su una singola chiamata e un chiaro vincitore su più valori).




0

Hai pensato di dichiarare le tabelle di ricerca per le due funzioni? Dovresti ancora "calcolare" sin (x) e cos (x), ma sarebbe decisamente più veloce, se non hai bisogno di un alto grado di precisione.


0

Il compilatore MSVC può utilizzare le funzioni SSE2 (interne)

 ___libm_sse2_sincos_ (for x86)
 __libm_sse2_sincos_  (for x64)

nelle build ottimizzate se sono specificati i flag appropriati del compilatore (al minimo / O2 / arch: SSE2 / fp: fast). I nomi di queste funzioni sembrano implicare che non calcolano sin e cos separati, ma entrambi "in un passaggio".

Per esempio:

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

Assembly (per x86) con / fp: fast:

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 (per x86) senza / fp: veloce ma con / fp: precise invece (che è l'impostazione predefinita) chiama separate sin e 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

Quindi / fp: fast è obbligatorio per l'ottimizzazione sincos.

Ma tieni presente che

___libm_sse2_sincos_

forse non è preciso come

__libm_sse2_sin_precise
__libm_sse2_cos_precise

a causa della mancanza "precisa" alla fine del suo nome.

Sul mio sistema "leggermente" più vecchio (Intel Core 2 Duo E6750) con l'ultimo compilatore MSVC 2019 e ottimizzazioni appropriate, il mio benchmark mostra che la chiamata sincos è circa 2,4 volte più veloce delle chiamate sin e cos separate.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.