2013-07-26 21 views
9

Mi chiedevo se lo exp() è più veloce del più generale pow(). Corro benchmark veloce su JsPerf http://jsperf.com/pow-vs-exp e ha mostrato risultati interessanti per me.Prestazioni Pow() vs. exp()

Math.exp(logBase * exponent); // fastest 
Math.exp(Math.log(base) * exponent); // middle 
Math.pow(base, exponent); // slowest 

So che i risultati saranno pesantemente variare sull'architettura e il linguaggio, ma io sono interessato a punto di vista teorico anche. pow(a, b) implementato come exp(log(a) * b) o c'è un modo più intelligente di come calcolare direttamente la potenza "direttamente" (in C++, C# o JavaScript). Ci sono istruzioni CPU per exp, log o pow su alcune architetture?

Per quanto ne so, sia exp() sia log() vengono calcolati utilizzando alcune serie di Taylor e sono piuttosto costosi da calcolare. Questo mi fa credere che per la base costante di potere, questo codice

double logBase = log(123.456); 
for (int i = 0; i < 1024; ++i) { 
    exp(logBase * 654.321); 
} 

è meglio di questo

for (int i = 0; i < 1024; ++i) { 
    pow(123.456, 654.321); 
} 

E 'questo presupposto corretto?

+4

Non sarei sorpreso se una di queste opzioni fosse significativamente più accurata delle altre. – delnan

+1

Ho margine di errore intorno al 2-5%. Prova a eseguire test poche volte.Ma il punto di riferimento è ovviamente tutt'altro che perfetto. Ecco perché sono interessato alla teoria alla base di questo. E anche la precisione è una domanda interessante. – NightElfik

+0

Questo dipenderà davvero dai dettagli di implementazione. La tua domanda su JavaScript è specifica? –

risposta

9

Sì, exp sarà più veloce di pow in generale.

Le funzioni exp e log saranno ottimizzate per la piattaforma di destinazione; molte tecniche possono essere usate come Pade approssimazione, riduzione lineare o binario seguita da approssimazione, ecc

La funzione pow sarà generalmente implementato come exp(log(a) * b) come dici, quindi è ovviamente più lento di soli exp.Esistono molti casi speciali per pow come esponenti negativi, esponenti integrali, esponenti pari a 1/2 o 1/3, ecc. Questi rallenteranno ulteriormente pow nel caso generale perché questi test sono costosi.

Vedere this SO question on pow.

1

Come risposta parziale, ci sono istruzioni per exp, log o pow su alcune architetture sì. Tuttavia, ciò non significa necessariamente molto.

Ad esempio, su x86 c'è

  • f2xm1 che calcola 2 x - 1
  • fscale che calcola y * 2 (int) x
  • fyl2x che calcola y * log x
  • fyl2xp1 che calcola y * log (x + 1) (ha restrizioni sul campo di immissione)

Tuttavia, non sono molto utilizzati. Varia dall'architettura all'architettura, ma non è mai veloce. Come esempio più estremo, fyl2x ha una latenza di 724 su Sandy Bridge (piuttosto recente!), In quel momento sullo stesso processore si potevano fare circa 700 aggiunte indipendenti in virgola mobile, o circa 240 aggiunte in virgola mobile dipendenti, o circa 2000 indipendenti semplici operazioni su interi.

Si tratta di cose brutte, ma in genere sono lente. Abbastanza lento che un'implementazione manuale può batterli o almeno non perdere significativamente.

Inoltre, il codice FPU sta lentamente scomparendo a favore del codice SSE. Non ci sono equivalenti SSE di quelle istruzioni.

+0

SSE incorpora una FPU e ha già ampiamente sostituito il codice * x87 *, motivo per cui Intel non si sforza di rendere 'fyl2x' veloce. – Potatoswatter

+0

@Potatoswatter sembra suggerire che chiamare codice x87 "codice FPU" non sia corretto, tuttavia, ciò è comunemente fatto. È una distinzione valida, SSE non usa la vecchia FPU. Ovviamente è implementato con le stesse unità funzionali sulla CPU, ma logicamente sono completamente separati. – harold

+0

Potrebbe o potrebbe non esserlo. In entrambi i casi, SSE è un set di istruzioni per una FPU (vettoriale), quindi chiamare il codice x87 "FPU code" per distinguerlo da SSE è fuorviante o semplicemente confusionario. Più precisamente, x87 è irrilevante perché obsoleto. – Potatoswatter

1

Indipendentemente dai dettagli dell'architettura, Math.pow deve fare di più in termini di controllo degli errori (ad esempio, cosa succede se la base è negativa?). di Math.exp (e come tale mi aspetto che pow sia più lento).

parti pertinenti della specifica:

http://ecma-international.org/ecma-262/5.1/#sec-15.8.2.8

15.8.2.8 exp (x)

Restituisce un'approssimazione dipendente dall'implementazione al esponenziale funzione di x (e elevato alla potenza di x, dove e è la base dei logaritmi naturali ).

Se x è NaN, il risultato è NaN. Se x è +0, il risultato è 1. Se x è -0, il risultato è 1. Se x è + ∞, il risultato è + ∞. Se x è -∞, il risultato è +0.

http://ecma-international.org/ecma-262/5.1/#sec-15.8.2.13

15.8.2.13 pow (x, y)

restituisce un'approssimazione dipendente dall'implementazione al risultato di aumentare x alla potenza y.

Se y è NaN, il risultato è NaN. Se y è +0, il risultato è 1, anche se x è NaN. Se y è -0, il risultato è 1, anche se x è NaN. Se x è NaN e y è diverso da zero, il risultato è NaN. Se abs (x)> 1 e y è + ∞, il risultato è + ∞. Se abs (x)> 1 e y è -∞, il risultato è +0. Se abs (x) == 1 e y è + ∞, il risultato è NaN. Se abs (x) == 1 e y è -∞, il risultato è NaN. Se abs (x) < 1 e y è + ∞, il risultato è +0. Se abs (x) < 1 e y è -∞, il risultato è + ∞. Se x è + ∞ e y> 0, il risultato è + ∞. Se x è + ∞ e y < 0, il risultato è +0. Se x è -∞ e y> 0 e y è un numero intero dispari, il risultato di è -∞. Se x è -∞ e y> 0 e y non è un numero intero dispari, il risultato di è + ∞. Se x è -∞ e y < 0 e y è un numero intero dispari, il risultato è -0. Se x è -∞ e y < 0 e y non è un numero intero dispari, il risultato è +0. Se x è +0 e y> 0, il risultato è +0. Se x è +0 e y < 0, il risultato è + ∞. Se x è -0 e y> 0 e y è un numero intero dispari, il risultato è -0. Se x è -0 e y> 0 e y non è un numero intero dispari, il risultato è +0. Se x è -0 e y < 0 e y è un numero intero dispari, il risultato è -∞. Se x è -0 e y < 0 e y non è un numero intero dispari, il risultato è + ∞. Se x e x è finito e y è finito e y non è un numero intero, il risultato è NaN.

+0

Grazie, questo è interessante! Questo è probabilmente il motivo per cui 'Math.exp (Math.log (base) * esponente)' è più veloce di 'Math.pow (base, esponente)'. Potrebbe non calcolare correttamente tutti i casi speciali, ma non mi interessa comunque. – NightElfik

+0

@NightElfik indipendentemente dalla risposta alla domanda, a meno che tu non stia pianificando di fare un lavoro numerico pesante in JS, dubito che il tempo di esecuzione di 'exp' e' pow' domini il tempo di esecuzione del tuo script. Ricorda la citazione di knuth "l'ottimizzazione prematura è la radice di tutti i mali" – SheetJS

+0

Lo so molto bene. Anche se questa parte del mio codice è molto usata, stavo vagando più sulla teoria alla base di questo fenomeno piuttosto che in realtà cercando di accelerare il mio codice. Prima di qualsiasi ottimizzazione delle prestazioni, faccio il profiling prima. – NightElfik