Qual è più veloce in Python: x **. 5 o math.sqrt (x)?


188

Me lo stavo chiedendo da tempo. Come dice il titolo, quale è più veloce, la funzione effettiva o semplicemente elevando a metà potenza?

AGGIORNARE

Non si tratta di ottimizzazione prematura. Questa è semplicemente una domanda su come funziona effettivamente il codice sottostante. Qual è la teoria di come funziona il codice Python?

Ho inviato un'email a Guido van Rossum perché volevo davvero conoscere le differenze tra questi metodi.

La mia email:

Esistono almeno 3 modi per eseguire una radice quadrata in Python: math.sqrt, l'operatore '**' e pow (x, .5). Sono solo curioso delle differenze nell'attuazione di ciascuno di questi. Quando si tratta di efficienza, che è meglio?

La sua risposta:

pow e ** sono equivalenti; math.sqrt non funziona per numeri complessi e si collega alla funzione C sqrt (). Per quanto riguarda quale è più veloce, non ho idea ...


81
È fantastico che Guido risponda alle e-mail.
Evan Fosmark,

3
Evan, sono stato sorpreso di aver ricevuto una risposta
No,

11
Non credo sia una cattiva domanda. Ad esempio, x * x è 10 volte più veloce di x ** 2. La leggibilità è un problema in questa situazione, quindi perché non fare in modo rapido?
TM.

12
Casey, sono con te per la "ottimizzazione prematura". :) La tua domanda non sembra un'ottimizzazione precoce per me: non c'è rischio che nessuna delle varianti violi il codice. È più una questione di sapere meglio cosa fai (in termini di tempo di esecuzione) quando scegli pow () piuttosto che math.sqrt ().
Eric O Lebigot,

8
Non si tratta di un'ottimizzazione prematura, ma piuttosto di evitare la pessimizzazione prematura (rif. N. 28, standard di codifica C ++, A.Alexandrescu). Se math.sqrtè una routine più ottimizzata (così com'è) ed esprime l'intento in modo più chiaro, dovrebbe sempre essere preferita x**.5. Non è prematura l'ottimizzazione per sapere cosa scrivi e scegliere l'alternativa che è più veloce e fornisce maggiore chiarezza del codice. In tal caso, devi discutere altrettanto bene del perché avresti scelto le altre alternative.
swalog,

Risposte:


90

math.sqrt(x)è significativamente più veloce di x**0.5.

import math
N = 1000000
%%timeit
for i in range(N):
    z=i**.5

10 loop, meglio di 3: 156 ms per loop

%%timeit
for i in range(N):
    z=math.sqrt(i)

10 loop, meglio di 3: 91,1 ms per loop

Utilizzo di Python 3.6.9 ( notebook ).


L'ho eseguito 3 volte su codepad.org e tutte e tre le volte a () era molto più veloce di b ().
Jeremy Ruten,

10
Il modulo timeit standard è tuo amico. Evita insidie ​​comuni quando si tratta di misurare i tempi di esecuzione!
Eric O Lebigot,

1
Ecco i risultati del tuo script: zoltan @ host: ~ $ python2.5 p.py ha impiegato 0,183226 secondi ha impiegato 0,155829 secondi zoltan @ host: ~ $ python2,4 p.py ha impiegato 0,181142 secondi ha impiegato 0,153742 secondi zoltan @ host: ~ $ python2.6 p.py Ha impiegato 0,157436 secondi Ha impiegato 0,093905 secondi Sistema di destinazione: Ubuntu Linux CPU: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2.80GHz Come puoi vedere ho ottenuto risultati diversi. In base a ciò la tua risposta non è generica.
zoli2k,

2
Codepad è un ottimo servizio, ma orribile per le prestazioni di temporizzazione, intendo chissà quanto sarà occupato il server in un determinato momento. Ogni corsa potrebbe potenzialmente dare risultati molto diversi
adamJLev

1
Ho aggiunto il confronto delle prestazioni di x **. 5 vs sqrt (x) per gli interpreti py32, py31, py30, py27, py26, pypy, jython, py25, py24 su Linux. gist.github.com/783011
jfs

19
  • prima regola di ottimizzazione: non farlo
  • seconda regola: non farlo ancora

Ecco alcuni tempi (Python 2.5.2, Windows):

$ python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
1000000 loops, best of 3: 0.445 usec per loop

$ python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
1000000 loops, best of 3: 0.574 usec per loop

$ python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
1000000 loops, best of 3: 0.727 usec per loop

Questo test mostra che x**.5è leggermente più veloce di sqrt(x).

Per Python 3.0 il risultato è l'opposto:

$ \Python30\python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
1000000 loops, best of 3: 0.803 usec per loop

$ \Python30\python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
1000000 loops, best of 3: 0.695 usec per loop

$ \Python30\python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
1000000 loops, best of 3: 0.761 usec per loop

math.sqrt(x)è sempre più veloce rispetto x**.5a un altro computer (Ubuntu, Python 2.6 e 3.1):

$ python -mtimeit -s"from math import sqrt; x = 123" "x**.5"
10000000 loops, best of 3: 0.173 usec per loop
$ python -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
10000000 loops, best of 3: 0.115 usec per loop
$ python -mtimeit -s"import math; x = 123" "math.sqrt(x)"
10000000 loops, best of 3: 0.158 usec per loop
$ python3.1 -mtimeit -s"from math import sqrt; x = 123" "x**.5"
10000000 loops, best of 3: 0.194 usec per loop
$ python3.1 -mtimeit -s"from math import sqrt; x = 123" "sqrt(x)"
10000000 loops, best of 3: 0.123 usec per loop
$ python3.1 -mtimeit -s"import math; x = 123" "math.sqrt(x)"
10000000 loops, best of 3: 0.157 usec per loop

10

Quante radici quadrate stai davvero suonando? Stai cercando di scrivere un motore grafico 3D in Python? In caso contrario, perché scegliere un codice criptico rispetto a un codice facile da leggere? La differenza di tempo sarebbe inferiore a quella che chiunque potrebbe notare in quasi tutte le applicazioni che potrei prevedere. Non intendo davvero porre la tua domanda, ma sembra che tu stia andando un po 'troppo avanti con l'ottimizzazione prematura.


16
non mi sembra di fare un'ottimizzazione prematura. È più una semplice questione di decidere tra 2 diversi metodi, che, in media, saranno più veloci.
No, il

2
Kibbee: è sicuramente una domanda valida, ma condivido il tuo sgomento per il numero di domande su Stack Overflow che implicano che il richiedente sta eseguendo tutti i tipi di ottimizzazione prematura. È sicuramente una grande percentuale delle domande poste per ogni lingua.
Eli Courtwright,

2
Math.sqrt (x) è più facile da leggere di x ** 0,5? Penso che siano entrambi ovviamente abbastanza radice quadrata ... almeno se hai comunque familiarità con Python. Non chiamare operatori python standard come ** "criptici" solo perché non hai familiarità con Python.
TM.

5
Non credo che l'operatore ** sia criptico. Penso che sollevare qualcosa all'esponente 0,5 come metodo per far diventare la radice quadrata un po 'criptica a quelli che non tengono il passo con la matematica.
Kibbee,

13
E se stia realizzando un motore 3D in Python?
Chris Burt-Brown,

9

In questi micro-benchmark, math.sqrtsarà più lento, a causa del leggero tempo impiegato per la ricerca sqrtnello spazio dei nomi matematici. Puoi migliorarlo leggermente con

 from math import sqrt

Anche allora, eseguendo alcune variazioni nel tempo, mostra un leggero vantaggio prestazionale (4-5%) x**.5

È interessante notare che sta facendo

 import math
 sqrt = math.sqrt

accelerato ancora di più, con una differenza di velocità dell'1%, con scarso significato statistico.


Ripeterò Kibbee e dirò che questa è probabilmente un'ottimizzazione prematura.


7

In python 2.6 la (float).__pow__() funzione usa la pow()funzione C e le math.sqrt()funzioni usa la Csqrt() funzione

Nel compilatore glibc l'implementazione di pow(x,y)è abbastanza complessa ed è ben ottimizzata per vari casi eccezionali. Ad esempio, la chiamata a C pow(x,0.5)chiama semplicemente la sqrt()funzione.

La differenza nella velocità di utilizzo .**o math.sqrtè causata dagli involucri utilizzati attorno alle funzioni C e la velocità dipende fortemente dai flag di ottimizzazione / compilatore C utilizzati sul sistema.

Modificare:

Ecco i risultati dell'algoritmo di Claudiu sulla mia macchina. Ho ottenuto risultati diversi:

zoltan@host:~$ python2.4 p.py 
Took 0.173994 seconds
Took 0.158991 seconds
zoltan@host:~$ python2.5 p.py 
Took 0.182321 seconds
Took 0.155394 seconds
zoltan@host:~$ python2.6 p.py 
Took 0.166766 seconds
Took 0.097018 seconds

4

Per quello che vale (vedi la risposta di Jim). Sulla mia macchina, eseguendo Python 2.5:

PS C:\> python -m timeit -n 100000 10000**.5
100000 loops, best of 3: 0.0543 usec per loop
PS C:\> python -m timeit -n 100000 -s "import math" math.sqrt(10000)
100000 loops, best of 3: 0.162 usec per loop
PS C:\> python -m timeit -n 100000 -s "from math import sqrt" sqrt(10000)
100000 loops, best of 3: 0.0541 usec per loop

4

usando il codice di Claudiu, sulla mia macchina anche con "from math import sqrt" x **. 5 è più veloce ma l'uso di psyco.full () sqrt (x) diventa molto più veloce, almeno del 200%


3

Molto probabilmente math.sqrt (x), perché è ottimizzato per il rooting quadrato.

I benchmark ti forniranno la risposta che stai cercando.


3

Qualcuno ha commentato la "radice quadrata veloce di Newton-Raphson" di Quake 3 ... L'ho implementato con i tipi, ma è super lento rispetto alle versioni native. Proverò alcune ottimizzazioni e implementazioni alternative.

from ctypes import c_float, c_long, byref, POINTER, cast

def sqrt(num):
 xhalf = 0.5*num
 x = c_float(num)
 i = cast(byref(x), POINTER(c_long)).contents.value
 i = c_long(0x5f375a86 - (i>>1))
 x = cast(byref(i), POINTER(c_float)).contents.value

 x = x*(1.5-xhalf*x*x)
 x = x*(1.5-xhalf*x*x)
 return x * num

Ecco un altro metodo che usa struct, esce circa 3,6 volte più veloce della versione di tipo, ma comunque 1/10 della velocità di C.

from struct import pack, unpack

def sqrt_struct(num):
 xhalf = 0.5*num
 i = unpack('L', pack('f', 28.0))[0]
 i = 0x5f375a86 - (i>>1)
 x = unpack('f', pack('L', i))[0]

 x = x*(1.5-xhalf*x*x)
 x = x*(1.5-xhalf*x*x)
 return x * num

1

I risultati di Claudiu differiscono dai miei. Sto usando Python 2.6 su Ubuntu su una vecchia macchina P4 2.4Ghz ... Ecco i miei risultati:

>>> timeit1()
Took 0.564911 seconds
>>> timeit2()
Took 0.403087 seconds
>>> timeit1()
Took 0.604713 seconds
>>> timeit2()
Took 0.387749 seconds
>>> timeit1()
Took 0.587829 seconds
>>> timeit2()
Took 0.379381 seconds

sqrt è sempre più veloce per me ... Anche Codepad.org ADESSO sembra concordare sul fatto che sqrt, nel contesto locale, sia più veloce ( http://codepad.org/6trzcM3j ). Sembra che Codepad stia attualmente eseguendo Python 2.5. Forse stavano usando 2.4 o più vecchi quando Claudiu ha risposto per la prima volta?

In effetti, anche usando math.sqrt (i) al posto di arg (i), ottengo ancora tempi migliori per sqrt. In questo caso timeit2 () ha impiegato tra 0,53 e 0,55 secondi sulla mia macchina, che è comunque migliore delle cifre 0,56-0,60 di timeit1.

Direi, su Python moderno, usare math.sqrt e sicuramente lo porterò al contesto locale, sia con somevar = math.sqrt o con da sqlt di importazione matematica.


1

La cosa Pythonic per cui ottimizzare è la leggibilità. Per questo penso che l'uso esplicito della sqrtfunzione sia il migliore. Detto questo, esaminiamo comunque le prestazioni.

Ho aggiornato il codice di Claudiu per Python 3 e ho anche reso impossibile ottimizzare i calcoli (cosa che un buon compilatore Python potrebbe fare in futuro):

from sys import version
from time import time
from math import sqrt, pi, e

print(version)

N = 1_000_000

def timeit1():
  z = N * e
  s = time()
  for n in range(N):
    z += (n * pi) ** .5 - z ** .5
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

def timeit2():
  z = N * e
  s = time()
  for n in range(N):
    z += sqrt(n * pi) - sqrt(z)
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

def timeit3(arg=sqrt):
  z = N * e
  s = time()
  for n in range(N):
    z += arg(n * pi) - arg(z)
  print (f"Took {(time() - s):.4f} seconds to calculate {z}")

timeit1()
timeit2()
timeit3()

I risultati variano, ma un output di esempio è:

3.6.6 (default, Jul 19 2018, 14:25:17) 
[GCC 8.1.1 20180712 (Red Hat 8.1.1-5)]
Took 0.3747 seconds to calculate 3130485.5713865166
Took 0.2899 seconds to calculate 3130485.5713865166
Took 0.2635 seconds to calculate 3130485.5713865166

Provate voi stessi.


0

Il problema che SQRMINSUM ho risolto di recente richiede il calcolo ripetuto della radice quadrata su un set di dati di grandi dimensioni. I 2 invii più vecchi della mia storia , prima di aver apportato altre ottimizzazioni, differiscono esclusivamente sostituendo ** 0,5 con sqrt (), riducendo così il tempo di esecuzione da 3,74 a 0,51 secondi in PyPy. Questo è quasi il doppio del già massiccio miglioramento del 400% misurato da Claudiu.


0

Naturalmente, se uno ha a che fare con valori letterali e ha bisogno di un valore costante, il runtime di Python può pre-calcolare il valore al momento della compilazione, se è scritto con gli operatori, in questo caso non è necessario profilare ciascuna versione:

In [77]: dis.dis(a)                                                                                                                       
  2           0 LOAD_CONST               1 (1.4142135623730951)
              2 RETURN_VALUE

In [78]: def a(): 
    ...:     return 2 ** 0.5 
    ...:                                                                                                                                  

In [79]: import dis                                                                                                                       

In [80]: dis.dis(a)                                                                                                                       
  2           0 LOAD_CONST               1 (1.4142135623730951)
              2 RETURN_VALUE

-3

Ciò che sarebbe ancora più veloce è se andassi su math.py e copiassi la funzione "sqrt" nel tuo programma. Ci vuole tempo perché il tuo programma trovi math.py, quindi aprilo, trova la funzione che stai cercando e poi riportala al tuo programma. Se quella funzione è più veloce anche con i passaggi di "ricerca", allora la funzione stessa deve essere terribilmente veloce. Probabilmente ti ridurrà il tempo a metà. In sintesi:

  1. Vai a math.py
  2. Trova la funzione "sqrt"
  3. Copialo
  4. Incolla la funzione nel tuo programma come sqrt finder.
  5. Tempo.

1
Non funzionerà; vedi stackoverflow.com/q/18857355/3004881 . Nota anche la citazione nella domanda originale che dice che è un collegamento a una funzione C. Inoltre, in che modo la copia del codice sorgente della funzione potrebbe essere diversa from math import sqrt?
Dan Getz,

Non lo avrei detto, solo per chiarire esattamente quale fosse la differenza nel chiamare le due funzioni.
PyGuy,
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.