2014-05-23 33 views
9

Poiché la funzione per il calcolo della funzione sin(x) in x86 risale all'era Pentium e apparentemente non utilizza nemmeno i registri SSE, mi chiedevo se esistesse un set di istruzioni più nuovo e migliore per il calcolo delle funzioni trigonometriche.Un fsin più veloce ma meno accurato per Intel asm?

Sono abituato a codificare in C++ e fare alcune ottimizzazioni asm, quindi tutto ciò che si adatta a una pipeline a partire da C++, a C a asm, funzionerà per me.

Grazie.


Sono sotto Linux a 64 bit, per ora, con gcc e clang (anche clang dura in realtà non offrono alcuna ottimizzazione legati FPU per quanto ne so).

EDIT

  • Ho già implementato una funzione sin, di solito è 2 volte più veloce std::sin anche con sse on.
  • La mia funzione non è mai più lento di fsin, anche dura fsin di solito è più accurato, ma considerando che fsin mai sorpassa il mio sin implementazione, terrò il mio sin per ora, anche il mio sin è completamente portatile dove fsin è per solo x86 .
  • Ho bisogno di questo per il calcolo in tempo reale, quindi scambierò precisione per velocità, penso che starò bene con 4-5 decimali di precisione.
  • no a un approccio basato su tabella, non lo sto utilizzando, svita la cache, rende tutto più lento, nessun algoritmo basato su accesso alla memoria o tabelle di ricerca per favore.
+1

Questo potrebbe rivelarsi utile: ["Funzioni trigonometriche veloci che utilizzano le istruzioni SSE2 di Intel"] (http://users.ece.utexas.edu/~adnan/comm/fast-trigonometric-functions-using.pdf) –

+0

@AlexReinking grazie ma quel documento sembra un riepilogo di diverse opzioni più una mezza pagina di codice che non credo sarà utile, almeno nel mio caso. – user2485710

+1

Puoi essere più specifico sul motivo per cui pensi che SSE2 non aiuti il ​​tuo caso? –

risposta

5

Se stai bene con un'approssimazione (sto assumendo sei, se si sta cercando di battere l'hardware), si dovrebbe dare un'occhiata a sin implementazione di Nick a DevMaster:

http://devmaster.net/posts/9648/fast-and-accurate-sine-cosine

Ha due versioni: un metodo "fast & sloppy" e un metodo "slow & accurato". Una coppia risponde che qualcuno stima gli errori relativi rispettivamente del 12% e dello 0,2%. Ho eseguito personalmente un'implementazione e ho trovato runtime di 1/14 e 1/8 dell'hardware sulla mia macchina.

Spero che questo aiuti!

PS: Se si esegue questa operazione da soli, è possibile refactoring il metodo lento/accurato per evitare una moltiplicazione e lieve miglioramento rispetto alla versione di Nick, ma non mi ricordo esattamente come ...

+0

beh questa è una lettura lunga, la sto leggendo ma per ora penso che avrò bisogno di un po 'di tempo per elaborare quello e le opzioni correlate. Ma sembra che quelle persone siano più o meno sviluppatrici di giochi e ne sono abbastanza contenti. – user2485710

+1

"è possibile rifattorizzare il metodo lento/preciso per evitare una moltiplicazione e migliorare leggermente la versione di Nick" Quando la forma di Horner è un miglioramento rispetto al proprio schema di valutazione polinomiale, si dovrebbe evitare affermazioni audaci sulla cosiddetta implementazione "rapida e accurata". Il titolo di questo post del blog dovrebbe essere "veloce e impreciso sinusoidale", poiché è quello che sono entrambe le versioni. –

+0

@PascalCuoq tutte le approssimazioni sono per definizione meno accurate, inoltre nel mondo informatico non so come le cose possano essere diverse. – user2485710

11

Se bisogno un'approssimazione sinusoidale ottimizzata per la precisione assoluta oltre -π ... π, uso:

x * (1 + x * x * (-0.1661251158026961831813227851437597220432 + x * x * (8.03943560729777481878247432892823524338e-3 + x * x * -1 .4941402004593877749503989396238510717e-4))

Può essere implementato con:

float xx = x * x; 
float s = x + (x * xx) * (-0.16612511580269618f + xx * (8.0394356072977748e-3f + xx * -1.49414020045938777495e-4f)); 

E forse optimized depending on the characteristics of your target architecture. Inoltre, non annotato nel post del blog collegato, se si sta implementando questo in assembly, utilizzare l'istruzione FMADD. Se si implementa in C o C++, se si utilizza, ad esempio, la funzione standard C99 fmaf(), assicurarsi che sia generato FMADD. La versione emulata è molto più costosa di una moltiplicazione e di un'aggiunta, perché ciò che fa fmaf() non è esattamente equivalente alla moltiplicazione seguita dall'addizione (quindi sarebbe scorretto implementarla semplicemente così).

La differenza tra sin (x) e detti polinomiale tra grafici -π e ¸ così:

graphpipi

Il polinomio è ottimizzato per ridurre la differenza tra questo e sin (x) tra -π e π, non solo qualcosa che qualcuno pensava fosse una buona idea.

Se è necessario solo l'intervallo di definizione [-1 ... 1], il polinomio può essere reso più preciso in tale intervallo ignorando il resto. Esecuzione the optimization algorithm ancora per questo intervallo di definizione produce:

x * (1 + x * x * (-1.666659904470566774477504230733785739156e-1 + x * x * (8.329797530524482484880881032235130379746e-3 + x * x * (- 1.928379009208489415662312713847811393721e-4)))

il grafico errore assoluto:

graph11

Se questo è troppo preciso per voi, è possibile optimize a polynomial of lower degree for the same objective Poi l'errore assoluto sarà più grande, ma vi farà risparmiare una moltiplicazione o due..

+0

Non riesco a seguire il tuo ragionamento, quale algoritmo hai scelto per derivare la prima e le altre formule? Ricorda che ho bisogno di farlo per tutte le altre funzioni, quindi ho bisogno di un algoritmo. – user2485710

+2

@ user2485710 Bene, la tua domanda riguarda il peccato, quindi ho risposto al peccato. Ad ogni modo, il metodo utilizzato è l'algoritmo Remez, e ciò che fornisce è spiegato molto chiaramente in un link che la mia risposta già fornisce: http://lolengine.net/blog/2011/12/21/better-function-approximations. Come funziona non è necessario capire per usarlo (io no). –

+1

@ user2485710 Ciò che ** è ** necessario capire sono i principi dell'approssimazione polinomiale (altrimenti si finisce per tentare di approssimare il peccato con un polinomio della forma aX^2 + bX e si deve chiamare 'abs()' ovunque e è ridicolo, come nella "versione di Nick" dalla risposta di Xavier Holt). Hai anche bisogno di informazioni di base sul punto di virgola mobile in modo che tu sappia che applicare il coefficiente di X a 1 è vantaggioso. Ho usato LolRemez, disponibile dal link che ho già fornito, ma è complicato usarlo correttamente, a causa di tutto quanto sopra –