O que é mais rápido no Python: x **. 5 ou math.sqrt (x)?


188

Eu estive pensando nisso há algum tempo. Como o título diz, o que é mais rápido, a função real ou simplesmente aumentar para a metade da potência?

ATUALIZAR

Não se trata de otimização prematura. Isso é simplesmente uma questão de como o código subjacente realmente funciona. Qual é a teoria de como o código Python funciona?

Enviei um email a Guido van Rossum porque eu realmente queria saber as diferenças nesses métodos.

Meu email:

Existem pelo menos três maneiras de criar uma raiz quadrada no Python: math.sqrt, o operador '**' e pow (x, .5). Estou apenas curioso sobre as diferenças na implementação de cada uma delas. Quando se trata de eficiência, qual é melhor?

Sua resposta:

pow e ** são equivalentes; math.sqrt não funciona para números complexos e links para a função C sqrt (). Quanto a qual é mais rápido, não faço ideia ...


81
Isso é incrível, o Guido responde ao e-mail.
Evan Fosmark

3
Evan, fiquei surpreso eu tenho uma resposta
Nope

11
Eu não acho que essa seja uma pergunta ruim. Por exemplo, x * x é 10 vezes mais rápido que x ** 2. A legibilidade é um acerto nesta situação, por que não fazer o caminho mais rápido?
TM.

12
Casey, estou com você no assunto da "otimização prematura". :) Sua pergunta não me parece otimização prematura: não há risco de que alguma das variantes quebre seu código. É mais uma questão de conhecer melhor o que você faz (em termos de tempo de execução) quando escolhe pow () em vez de math.sqrt ().
Eric O Lebigot

8
Isso não é otimização prematura, mas evitar pessimização prematura (ref. 28, padrões de codificação C ++, A.Alexandrescu). Se math.sqrté uma rotina mais otimizada (como é) e expressa a intenção com mais clareza, sempre deve ser preferida x**.5. Não é uma otimização prematura saber o que você escreve e escolheu a alternativa que é mais rápida e oferece mais clareza de código. Nesse caso, você precisa argumentar igualmente bem por que escolheria as outras alternativas.
swalog

Respostas:


90

math.sqrt(x)é significativamente mais rápido que x**0.5.

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

10 loops, o melhor de 3: 156 ms por loop

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

10 loops, o melhor de 3: 91,1 ms por loop

Usando Python 3.6.9 ( notebook ).


Eu já o executei três vezes no codepad.org e todas as três vezes a () foram muito mais rápidas que b ().
Jeremy Ruten

10
O módulo timeit padrão é seu amigo. Evita armadilhas comuns quando se trata de medir o tempo de execução!
Eric O Lebigot

1
Aqui estão os resultados do seu script: zoltan @ host: ~ $ python2.5 p.py levou 0,183226 segundos levou 0,155829 segundos zoltan @ host: ~ $ python2,4 p.py levou 0,181142 segundos levou 0,153742 segundos zoltan @ host: ~ $ python2.6 p.py Tomou 0.157436 segundos Tomou 0.093905 segundos Sistema-alvo: Ubuntu Linux CPU: CPU Intel (R) Core (TM) 2 Duo T9600 a 2.80GHz Como você pode ver, obtive resultados diferentes. De acordo com isso, sua resposta não é genérica.
Zoli2k

2
O Codepad é um ótimo serviço, mas horrível para o desempenho do tempo, quero dizer quem sabe o quão ocupado o servidor estará em um determinado momento. Cada execução pode dar resultados muito diferentes
adamJLev

1
Adicionei comparação de desempenho de x ** .5 vs sqrt (x) para py32, py31, py30, py27, py26, pypy, jython, py25, py24 intérpretes no Linux. gist.github.com/783011
jfs

19
  • primeira regra de otimização: não faça
  • segunda regra: não faça isso ainda

Aqui estão alguns horários (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

Este teste mostra que x**.5é um pouco mais rápido que sqrt(x).

Para o Python 3.0, o resultado é o oposto:

$ \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 mais rápido que x**.5em outra máquina (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

Quantas raízes quadradas você realmente está realizando? Você está tentando escrever algum mecanismo de gráficos 3D em Python? Se não, então por que usar um código que é enigmático sobre um código fácil de ler? A diferença horária é menor do que qualquer um poderia notar em praticamente qualquer aplicativo que eu pudesse prever. Realmente não pretendo fazer sua pergunta, mas parece que você está indo longe demais com a otimização prematura.


16
Eu realmente não sinto que estou fazendo uma otimização prematura. É mais uma questão simples de escolher entre dois métodos diferentes, que, em média, serão mais rápidos.
Nope

2
Kibbee: é definitivamente uma pergunta válida, mas eu compartilho sua consternação com o número de perguntas no Stack Overflow que implicam que o solicitante está realizando todos os tipos de otimização prematura. Definitivamente, é uma grande porcentagem das perguntas feitas em todos os idiomas.
Eli Courtwright 29/11/08

2
Math.sqrt (x) é mais fácil de ler do que x ** 0,5? Eu acho que ambos são, obviamente, raiz quadrada ... pelo menos se você estiver familiarizado com python de qualquer maneira. Não chame operadores python padrão como ** "cryptic" apenas porque você não está familiarizado com python.
TM.

5
Eu não acho que o operador ** seja enigmático. Eu acho que elevar algo ao expoente 0,5 como um método para fazer com que a raiz quadrada seja um pouco enigmático para aqueles que não mantêm a matemática.
Kibbee

13
E se ele estiver criando um mecanismo 3D em Python?
Chris Burt-Brown

9

Nesses micro-benchmarks, math.sqrtserá mais lento, devido ao pouco tempo necessário para pesquisar sqrtno espaço para nome matemático. Você pode melhorar um pouco com

 from math import sqrt

Mesmo assim, executando algumas variações ao longo do tempo, mostra uma pequena vantagem de desempenho (4-5%) para x**.5

Curiosamente, fazendo

 import math
 sqrt = math.sqrt

acelerou ainda mais, com uma diferença de velocidade de 1%, com muito pouca significância estatística.


Vou repetir o Kibbee e dizer que essa é provavelmente uma otimização prematura.


7

No python 2.6, a (float).__pow__() função usa a pow()função C e as math.sqrt()funções usam a sqrt()função C.

No compilador glibc, a implementação de pow(x,y)é bastante complexa e está bem otimizada para vários casos excepcionais. Por exemplo, chamar C pow(x,0.5)simplesmente chama a sqrt()função

A diferença na velocidade de uso .**ou math.sqrté causada pelos wrappers usados ​​em torno das funções C e a velocidade depende fortemente dos sinalizadores de otimização / compilador C usados ​​no sistema.

Editar:

Aqui estão os resultados do algoritmo de Claudiu na minha máquina. Eu obtive 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

4

Para o que vale a pena (veja a resposta de Jim). Na minha máquina, executando o 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 o código de Claudiu, na minha máquina, mesmo com "from math import sqrt" x **. 5 é mais rápido, mas o uso de psyco.full () sqrt (x) se torna muito mais rápido, pelo menos em 200%


3

Provavelmente math.sqrt (x), porque é otimizado para o enraizamento quadrado.

Os benchmarks fornecerão a resposta que você está procurando.


3

Alguém comentou sobre a "raiz quadrada rápida de Newton-Raphson" do Quake 3 ... eu implementei com ctypes, mas é super lento em comparação com as versões nativas. Vou tentar algumas otimizações e implementações 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

Aqui está outro método usando struct, sai cerca de 3,6x mais rápido que a versão ctypes, mas ainda 1/10 da velocidade 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

1

Os resultados de Claudiu diferem dos meus. Estou usando o Python 2.6 no Ubuntu em uma antiga máquina P4 2.4Ghz ... Aqui estão meus 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

O sqrt é consistentemente mais rápido para mim ... Até o Codepad.org AGORA parece concordar que o sqrt, no contexto local, é mais rápido ( http://codepad.org/6trzcM3j ). O Codepad parece estar executando o Python 2.5 atualmente. Talvez eles estivessem usando o 2.4 ou mais quando Claudiu respondeu pela primeira vez?

De fato, mesmo usando math.sqrt (i) no lugar de arg (i), ainda tenho tempos melhores para o sqrt. Nesse caso, o timeit2 () levou entre 0,53 e 0,55 segundos na minha máquina, o que ainda é melhor do que os valores de 0,56-0,60 do timeit1.

Eu diria que, no Python moderno, use math.sqrt e traga-o definitivamente para o contexto local, com somevar = math.sqrt ou com math import sqrt.


1

O ideal para otimizar é a legibilidade. Para isso, acho melhor o uso explícito da sqrtfunção. Dito isto, vamos investigar o desempenho de qualquer maneira.

Atualizei o código de Claudiu para Python 3 e também impossibilitei otimizar os cálculos (algo que um bom compilador Python pode fazer no 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()

Os resultados variam, mas uma saída de amostra é:

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

Tente você mesmo.


0

O problema que SQRMINSUM que resolvi recentemente exige o cálculo da raiz quadrada repetidamente em um grande conjunto de dados. Os 2 envios mais antigos da minha história , antes de fazer outras otimizações, diferem apenas substituindo ** 0,5 por sqrt (), reduzindo assim o tempo de execução de 3,74s para 0,51s no PyPy. Isso é quase o dobro da já massiva melhoria de 400% que Claudiu mediu.


0

Obviamente, se alguém está lidando com literais e precisa de um valor constante, o tempo de execução do Python pode pré-calcular o valor em tempo de compilação, se ele estiver escrito com operadores - não há necessidade de criar um perfil de cada versão neste 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

-3

O que seria ainda mais rápido é se você entrou no math.py e copiou a função "sqrt" para o seu programa. Leva tempo para o seu programa encontrar math.py, abra-o, encontre a função que você está procurando e, em seguida, traga-a de volta ao programa. Se essa função for mais rápida, mesmo com as etapas de "pesquisa", a função em si deve ser extremamente rápida. Provavelmente reduzirá seu tempo pela metade. Em suma:

  1. Vá para math.py
  2. Encontre a função "sqrt"
  3. Copie
  4. Cole a função no seu programa como o localizador do sqrt.
  5. Time it.

1
Isso não vai funcionar; consulte stackoverflow.com/q/18857355/3004881 . Observe também a citação na pergunta original que diz que é um link para uma função C. Além disso, como poderia ser diferente copiar o código-fonte da função from math import sqrt?
Dan Getz

Eu não disse isso apenas para esclarecer exatamente qual era a diferença em chamar as duas funções.
PyGuy
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.