¿Cuál es más rápido en Python: x **. 5 o math.sqrt (x)?

188

Me he estado preguntando esto por algún tiempo. Como dice el título, ¿cuál es más rápido, la función real o simplemente elevar a la mitad la potencia?

ACTUALIZAR

Esto no es una cuestión de optimización prematura. Esto es simplemente una cuestión de cómo funciona realmente el código subyacente. ¿Cuál es la teoría de cómo funciona el código Python?

Le envié un correo electrónico a Guido van Rossum porque realmente quería saber las diferencias en estos métodos.

Mi email:

Hay al menos 3 formas de hacer una raíz cuadrada en Python: math.sqrt, el operador '**' y pow (x, .5). Tengo curiosidad por las diferencias en la implementación de cada uno de estos. Cuando se trata de eficiencia, ¿cuál es mejor?

Su respuesta:

pow y ** son equivalentes; math.sqrt no funciona para números complejos y enlaza con la función C sqrt (). En cuanto a cuál es más rápido, no tengo idea ...

No
fuente
81
Eso es increíble, el Guido responde al correo electrónico.
Evan Fosmark el
3
Evan, me sorprendió que me dieron una respuesta
Nop
11
No creo que esta sea una mala pregunta. Por ejemplo, x * x es 10 veces más rápido que x ** 2. La legibilidad es un cambio en esta situación, entonces, ¿por qué no hacerlo de manera rápida?
TM.
12
Casey, estoy contigo en lo de la "optimización prematura". :) Su pregunta no me parece una optimización prematura: no hay riesgo de que ninguna de las variantes rompa su código. Es más una cuestión de saber mejor lo que haces (en términos de tiempo de ejecución) cuando eliges pow () sobre math.sqrt ().
Eric O Lebigot
8
Esto no es una optimización prematura, sino más bien evitar la pesimismo prematura (ref. No. 28, estándares de codificación C ++, A.Alexandrescu). Si math.sqrtes una rutina más optimizada (tal como es) y expresa la intención con mayor claridad, siempre debe preferirse a ella x**.5. No es una optimización prematura saber lo que escribes, y elegir la alternativa que sea más rápida y proporcione más claridad de código. Si es así, debe argumentar igualmente bien por qué elegiría las otras alternativas.
swalog

Respuestas:

90

math.sqrt(x)es significativamente más rápido que x**0.5.

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

10 bucles, lo mejor de 3: 156 ms por bucle

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

10 bucles, lo mejor de 3: 91.1 ms por bucle

Usando Python 3.6.9 ( cuaderno ).

Claudiu
fuente
Ahora lo he ejecutado 3 veces en codepad.org y las tres veces a () fue mucho más rápido que b ().
Jeremy Ruten el
10
El módulo de timeit estándar es tu amigo. ¡Evita errores comunes a la hora de medir el tiempo de ejecución!
Eric O Lebigot
1
Aquí están los resultados de su script: zoltan @ host: ~ $ python2.5 p.py Tomó 0.183226 segundos Tomó 0.155829 segundos zoltan @ host: ~ $ python2.4 p.py Tomó 0.181142 segundos Tomó 0.153742 segundos zoltan @ host: ~ $ python2.6 p.py Tomó 0.157436 segundos Tomó 0.093905 segundos Sistema de destino: Ubuntu Linux CPU: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2.80GHz Como puede ver, obtuve resultados diferentes. Según esto, su respuesta no es genérica.
zoli2k
2
Codepad es un gran servicio, pero horrible para el rendimiento del tiempo, quiero decir quién sabe qué tan ocupado estará el servidor en un momento dado. Cada carrera podría dar resultados muy diferentes
adamJLev,
1
Agregué una comparación de rendimiento de x **. 5 vs sqrt (x) para los intérpretes py32, py31, py30, py27, py26, pypy, jython, py25, py24 en Linux. gist.github.com/783011
jfs
19
  • primera regla de optimización: no lo hagas
  • segunda regla: no lo hagas , todavía

Aquí hay algunos tiempos (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

Esta prueba muestra que x**.5es un poco más rápido que sqrt(x).

Para Python 3.0, el resultado es el opuesto:

$ \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)siempre es más rápido que x**.5en otra máquina (Ubuntu, Python 2.6 y 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
jfs
fuente
10

¿Cuántas raíces cuadradas realmente estás realizando? ¿Estás tratando de escribir algún motor de gráficos 3D en Python? Si no es así, ¿por qué elegir el código que es críptico sobre el código que es fácil de leer? La diferencia horaria es menor de lo que cualquiera podría notar en casi cualquier aplicación que pudiera prever. Realmente no pretendo dejar tu pregunta, pero parece que vas demasiado lejos con la optimización prematura.

Kibbee
fuente
16
Realmente no siento que estoy haciendo una optimización prematura. Es más una simple cuestión de decidir entre 2 métodos diferentes, que, en promedio, serán más rápidos.
No
2
Kibbee: definitivamente es una pregunta válida, pero comparto su consternación por la cantidad de preguntas en Stack Overflow que implican que el autor de la pregunta está realizando todo tipo de optimización prematura. Definitivamente es un gran porcentaje de las preguntas que se hacen para cada idioma.
Eli Courtwright
2
¿Es math.sqrt (x) más fácil de leer que x ** 0.5? Creo que ambos son, obviamente, raíz cuadrada ... al menos si estás familiarizado con Python de todos modos. No llame a los operadores estándar de python como ** "críptico" simplemente porque no está familiarizado con python.
TM.
55
No creo que el operador ** sea críptico. Creo que elevar algo al exponente 0.5 como un método para hacer que la raíz cuadrada sea un poco críptica para aquellos que no mantienen sus matemáticas.
Kibbee
13
¿Qué pasa si él está haciendo un motor 3D en Python?
Chris Burt-Brown
9

En estos micro-puntos de referencia, math.sqrtserá más lento, debido al poco tiempo que lleva buscar sqrten el espacio de nombres matemático. Puedes mejorarlo ligeramente con

 from math import sqrt

Aun así, sin embargo, ejecutando algunas variaciones a través del tiempo, muestra una ligera ventaja de rendimiento (4-5%) para x**.5

Curiosamente, haciendo

 import math
 sqrt = math.sqrt

acelere aún más, hasta una diferencia de velocidad del 1%, con muy poca significación estadística.


Repetiré Kibbee y diré que esta es probablemente una optimización prematura.

JimB
fuente
7

En python 2.6, la (float).__pow__() función usa la pow()función C y las math.sqrt()funciones usan la sqrt()función C.

En el compilador glibc, la implementación pow(x,y)es bastante compleja y está bien optimizada para varios casos excepcionales. Por ejemplo, llamar a C pow(x,0.5)simplemente llama a la sqrt()función.

La diferencia en la velocidad de uso .**o math.sqrtes causada por los envoltorios utilizados alrededor de las funciones de C y la velocidad depende en gran medida de los indicadores de optimización / compilador de C utilizados en el sistema.

Editar:

Aquí están los resultados del algoritmo de Claudiu en mi máquina. Obtuve resultados diferentes:

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
zoli2k
fuente
4

Por lo que vale (ver la respuesta de Jim). En mi máquina, ejecutando 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
zdan
fuente
4

usando el código de Claudiu, en mi máquina incluso con "from math import sqrt" x **. 5 es más rápido pero usando psyco.full () sqrt (x) se vuelve mucho más rápido, al menos en un 200%

No
fuente
3

Lo más probable es que math.sqrt (x), porque está optimizado para el enraizamiento cuadrado.

Los puntos de referencia le proporcionarán la respuesta que está buscando.

extraño
fuente
3

Alguien comentó sobre la "raíz cuadrada rápida de Newton-Raphson" del Quake 3 ... La implementé con ctypes, pero es muy lenta en comparación con las versiones nativas. Voy a probar algunas optimizaciones e implementaciones alternativas.

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

Aquí hay otro método que usa struct, sale aproximadamente 3.6 veces más rápido que la versión ctypes, pero aún 1/10 de la velocidad de 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
lunixbochs
fuente
1

Los resultados de Claudiu difieren de los míos. Estoy usando Python 2.6 en Ubuntu en una vieja máquina P4 2.4Ghz ... Aquí están mis resultados:

>>> 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 es consistentemente más rápido para mí ... Incluso Codepad.org NOW parece estar de acuerdo en que sqrt, en el contexto local, es más rápido ( http://codepad.org/6trzcM3j ). Codepad parece estar ejecutando Python 2.5 actualmente. ¿Quizás estaban usando 2.4 o más cuando Claudiu respondió por primera vez?

De hecho, incluso usando math.sqrt (i) en lugar de arg (i), todavía obtengo mejores tiempos para sqrt. En este caso, timeit2 () tardó entre 0,53 y 0,55 segundos en mi máquina, lo que sigue siendo mejor que las cifras de 0,56-0,60 de timeit1.

Yo diría que en Python moderno, use math.sqrt y definitivamente tráigalo al contexto local, ya sea con somevar = math.sqrt o con math import sqrt.

bobpaul
fuente
1

Lo pitónico para optimizar es la legibilidad. Para esto, creo que el uso explícito de la sqrtfunción es mejor. Dicho esto, investiguemos el rendimiento de todos modos.

Actualicé el código de Claudiu para Python 3 y también hice imposible optimizar los cálculos (algo que un buen compilador de Python puede hacer en el 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()

Los resultados varían, pero una salida de muestra es:

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

Inténtalo tú mismo.

hkBst
fuente
0

El problema SQRMINSUM que he resuelto recientemente requiere calcular la raíz cuadrada repetidamente en un gran conjunto de datos. Las 2 presentaciones más antiguas de mi historia , antes de hacer otras optimizaciones, difieren únicamente al reemplazar ** 0.5 con sqrt (), lo que reduce el tiempo de ejecución de 3.74s a 0.51s en PyPy. Esto es casi el doble de la mejora ya masiva del 400% que midió Claudiu.

Nadstratosfer Gonczy
fuente
0

Por supuesto, si se trata de literales y necesita un valor constante, el tiempo de ejecución de Python puede calcular previamente el valor en tiempo de compilación, si está escrito con operadores; no es necesario perfilar cada versión en este caso:

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
jsbueno
fuente
-3

Lo que sería aún más rápido es si ingresó a math.py y copió la función "sqrt" en su programa. A su programa le lleva tiempo encontrar math.py, luego abrirlo, encontrar la función que está buscando y luego traerla nuevamente a su programa. Si esa función es más rápida incluso con los pasos de "búsqueda", entonces la función en sí misma debe ser terriblemente rápida. Probablemente reducirá su tiempo a la mitad. En resumen:

  1. Ve a math.py
  2. Encuentra la función "sqrt"
  3. Cópialo
  4. Pegue la función en su programa como el buscador de sqrt.
  5. Cronométralo.
PyGuy
fuente
1
Eso no funcionará; ver stackoverflow.com/q/18857355/3004881 . También tenga en cuenta la cita en la pregunta original que dice que es un enlace a una función C. Además, ¿cómo podría ser diferente copiar el código fuente de la función from math import sqrt?
Dan Getz
No lo haría, dije eso solo para aclarar exactamente cuál era la diferencia al llamar a las dos funciones.
PyGuy