Quel est le plus rapide en Python: x **. 5 ou math.sqrt (x)?

195

Je me demande cela depuis un certain temps. Comme le titre l'indique, qu'est-ce qui est le plus rapide, la fonction réelle ou simplement monter à mi-puissance?

MISE À JOUR

Ce n'est pas une question d'optimisation prématurée. Il s'agit simplement de savoir comment fonctionne réellement le code sous-jacent. Quelle est la théorie du fonctionnement du code Python?

J'ai envoyé un e-mail à Guido van Rossum car je voulais vraiment connaître les différences entre ces méthodes.

Mon email:

Il y a au moins 3 façons de faire une racine carrée en Python: math.sqrt, l'opérateur '**' et pow (x, .5). Je suis simplement curieux de connaître les différences dans la mise en œuvre de chacun d'entre eux. En matière d'efficacité, quel est le meilleur?

Sa réponse:

pow et ** sont équivalents; math.sqrt ne fonctionne pas pour les nombres complexes et les liens vers la fonction C sqrt (). Quant à savoir lequel est le plus rapide, je n'ai aucune idée ...

Nan
la source
82
C'est génial que Guido réponde aux e-mails.
Evan Fosmark le
3
Evan, j'ai été surpris d'avoir une réponse
Non
12
Je ne pense pas que ce soit une mauvaise question. Par exemple, x * x est 10 fois plus rapide que x ** 2. La lisibilité est un jeu d'enfant dans cette situation, alors pourquoi ne pas le faire rapidement?
TM.
12
Casey, je suis avec vous sur la question de "l'optimisation prématurée". :) Votre question ne me semble pas une optimisation prématurée: il n'y a aucun risque que l'une des variantes casse votre code. Il s'agit plus de mieux savoir ce que vous faites (en termes de temps d'exécution) lorsque vous choisissez pow () plutôt que math.sqrt ().
Eric O Lebigot
8
Il ne s'agit pas d'une optimisation prématurée, mais plutôt d'éviter une pessimisation prématurée (réf. N ° 28, normes de codage C ++, A.Alexandrescu). S'il math.sqrts'agit d'une routine plus optimisée (telle quelle) et qui exprime l'intention plus clairement, elle doit toujours être préférée à x**.5. Il n'est pas prématuré de savoir ce que vous écrivez et de choisir l'alternative la plus rapide et offrant plus de clarté au code. Si tel est le cas, vous devez également expliquer pourquoi vous auriez choisi les autres alternatives.
swalog

Réponses:

93

math.sqrt(x)est nettement plus rapide que x**0.5.

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

10 boucles, meilleur de 3: 156 ms par boucle

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

10 boucles, meilleur de 3: 91,1 ms par boucle

Utilisation de Python 3.6.9 ( notebook ).

Claudiu
la source
Je l'ai maintenant exécuté 3 fois sur codepad.org et les trois fois a () était beaucoup plus rapide que b ().
Jeremy Ruten
13
Le module timeit standard est votre ami. Cela évite les pièges courants lorsqu'il s'agit de mesurer le temps d'exécution!
Eric O Lebigot
1
Voici les résultats de votre script: zoltan @ host: ~ $ python2.5 p.py A pris 0,183226 secondes A pris 0,155829 secondes zoltan @ host: ~ $ python2,4 p.py A pris 0,181142 secondes A pris 0,153742 secondes zoltan @ host: ~ $ python2.6 p.py A pris 0,157436 secondes A pris 0,093905 secondes Système cible: Ubuntu Linux CPU: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2,80 GHz Comme vous pouvez le voir, j'ai obtenu des résultats différents. D'après cela, votre réponse n'est pas générique.
zoli2k
3
Codepad est un excellent service, mais horrible pour les performances de synchronisation, je veux dire qui sait à quel point le serveur sera occupé à un moment donné. Chaque course pourrait potentiellement donner des résultats très différents
adamJLev
1
J'ai ajouté la comparaison des performances de x **. 5 vs sqrt (x) pour les interpréteurs py32, py31, py30, py27, py26, pypy, jython, py25, py24 sur Linux. gist.github.com/783011
jfs
19
  • première règle d'optimisation: ne le faites pas
  • deuxième règle: ne le faites pas encore

Voici quelques horaires (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

Ce test montre que x**.5c'est légèrement plus rapide que sqrt(x).

Pour le Python 3.0, le résultat est le contraire:

$ \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)est toujours plus rapide que x**.5sur une autre machine (Ubuntu, Python 2.6 et 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
la source
11

Dans ces micro-benchmarks, ce math.sqrtsera plus lent, en raison du peu de temps nécessaire pour rechercher le sqrtdans l'espace de noms mathématique. Vous pouvez l'améliorer légèrement avec

 from math import sqrt

Même dans ce cas, en exécutant quelques variations dans le temps, cela montre un léger avantage (4-5%) en termes de performances pour x**.5

Fait intéressant, faire

 import math
 sqrt = math.sqrt

l'a accéléré encore plus, à moins de 1% de différence de vitesse, avec très peu de signification statistique.


Je vais répéter Kibbee et dire que c'est probablement une optimisation prématurée.

JimB
la source
La raison pour laquelle la définition sqrtdans l'espace de noms local du programme l'accélère davantage est probablement à cause de l'ordre de résolution de la méthode: le compilateur vérifie d'abord si la fonction a été définie dans votre code, puis dans toutes les importations, donc si elle a été définie localement, cela prend moins de temps. recherche
Rotem Shalev
9

Combien de racines carrées jouez-vous vraiment? Essayez-vous d'écrire un moteur graphique 3D en Python? Sinon, pourquoi choisir un code cryptique plutôt qu'un code facile à lire? Le décalage horaire serait inférieur à ce que quiconque pourrait remarquer dans à peu près n'importe quelle application que je pourrais prévoir. Je ne veux vraiment pas poser votre question, mais il semble que vous allez un peu trop loin avec une optimisation prématurée.

Kibbee
la source
17
je n'ai pas vraiment l'impression de faire une optimisation prématurée. Il s'agit plus d'une simple question de choisir entre 2 méthodes différentes, qui, en moyenne, seront plus rapides.
Non
2
Kibbee: c'est certainement une question valable, mais je partage votre consternation face au nombre de questions sur Stack Overflow qui impliquent que le demandeur effectue toutes sortes d'optimisations prématurées. C'est certainement un grand pourcentage des questions posées pour chaque langue.
Eli Courtwright
2
Math.sqrt (x) est-il plus facile à lire que x ** 0.5? Je pense qu'ils sont tous les deux assez manifestement racine carrée ... du moins si vous êtes familier avec python de toute façon. N'appelez pas les opérateurs python standard comme ** "cryptic" simplement parce que vous n'êtes pas familier avec python.
TM.
5
Je ne pense pas que l'opérateur ** soit cryptique. Je pense qu'élever quelque chose à l'exposant 0,5 comme méthode pour que la racine carrée soit un peu cryptique pour ceux qui ne suivent pas leurs calculs.
Kibbee
14
Et s'il faisait un moteur 3D en Python?
Chris Burt-Brown
7

En python 2.6, la (float).__pow__() fonction utilise la pow()fonction C et les math.sqrt()fonctions utilisent la sqrt()fonction C.

Dans le compilateur glibc, l'implémentation de pow(x,y)est assez complexe et elle est bien optimisée pour divers cas exceptionnels. Par exemple, appeler C pow(x,0.5)appelle simplement la sqrt()fonction.

La différence de vitesse d'utilisation .**ou math.sqrtest causée par les wrappers utilisés autour des fonctions C et la vitesse dépend fortement des indicateurs d'optimisation / compilateur C utilisés sur le système.

Éditer:

Voici les résultats de l'algorithme de Claudiu sur ma machine. J'ai eu des résultats différents:

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
la source
4

Pour ce que ça vaut (voir la réponse de Jim). Sur ma machine, exécutant 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
la source
4

en utilisant le code de Claudiu, sur ma machine même avec "from math import sqrt" x **. 5 est plus rapide mais utiliser psyco.full () sqrt (x) devient beaucoup plus rapide, au moins de 200%

Nan
la source
3

Très probablement math.sqrt (x), car il est optimisé pour l'enracinement carré.

Benchmarks vous fournira la réponse que vous recherchez.

strager
la source
3

Quelqu'un a commenté la "racine carrée rapide de Newton-Raphson" de Quake 3 ... Je l'ai implémentée avec des ctypes, mais c'est super lent par rapport aux versions natives. Je vais essayer quelques optimisations et implémentations alternatives.

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

Voici une autre méthode utilisant struct, sort environ 3,6 fois plus vite que la version ctypes, mais toujours 1/10 de la vitesse 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
la source
1

Les résultats de Claudiu diffèrent des miens. J'utilise Python 2.6 sur Ubuntu sur une ancienne machine P4 2.4Ghz ... Voici mes résultats:

>>> 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 est toujours plus rapide pour moi ... Même Codepad.org NOW semble convenir que sqrt, dans le contexte local, est plus rapide ( http://codepad.org/6trzcM3j ). Codepad semble exécuter actuellement Python 2.5. Peut-être utilisaient-ils 2.4 ou plus quand Claudiu a répondu pour la première fois?

En fait, même en utilisant math.sqrt (i) à la place de arg (i), j'obtiens toujours de meilleurs temps pour sqrt. Dans ce cas, timeit2 () a pris entre 0,53 et 0,55 secondes sur ma machine, ce qui est toujours mieux que les chiffres de 0,56-0,60 de timeit1.

Je dirais que sur Python moderne, utilisez math.sqrt et mettez-le définitivement dans le contexte local, soit avec somevar = math.sqrt, soit avec from math import sqrt.

bobpaul
la source
1

La chose pythonique à optimiser est la lisibilité. Pour cela, je pense que l'utilisation explicite de la sqrtfonction est la meilleure. Cela dit, examinons quand même les performances.

J'ai mis à jour le code de Claudiu pour Python 3 et j'ai également rendu impossible l'optimisation des calculs (ce qu'un bon compilateur Python pourrait faire à l'avenir):

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()

Les résultats varient, mais un exemple de sortie est:

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

Essayez-le vous-même.

hkBst
la source
1

Bien sûr, si l'on a affaire à des littéraux et que l'on a besoin d'une valeur constante, le runtime Python peut pré-calculer la valeur au moment de la compilation, si elle est écrite avec des opérateurs - pas besoin de profiler chaque version dans ce cas:

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
la source
0

Le problème SQRMINSUM que j'ai résolu récemment nécessite le calcul de la racine carrée à plusieurs reprises sur un grand ensemble de données. Les 2 plus anciennes soumissions de mon histoire , avant que j'aie fait d'autres optimisations, diffèrent uniquement en remplaçant ** 0.5 par sqrt (), réduisant ainsi le temps d'exécution de 3.74s à 0.51s dans PyPy. C'est presque le double de l'amélioration déjà massive de 400% que Claudiu a mesurée.

Nadstratosfer Gonczy
la source
-3

Ce serait encore plus rapide si vous alliez dans math.py et copiiez la fonction «sqrt» dans votre programme. Il faut du temps à votre programme pour trouver math.py, puis l'ouvrir, trouver la fonction que vous recherchez, puis la ramener dans votre programme. Si cette fonction est plus rapide même avec les étapes de «recherche», alors la fonction elle-même doit être terriblement rapide. Cela réduira probablement votre temps de moitié. En résumé:

  1. Allez sur math.py
  2. Trouvez la fonction "sqrt"
  3. Copiez-le
  4. Collez la fonction dans votre programme en tant que chercheur sqrt.
  5. Chronométrez-le.
PyGuy
la source
1
Cela ne fonctionnera pas; voir stackoverflow.com/q/18857355/3004881 . Notez également la citation dans la question d'origine qui dit qu'il s'agit d'un lien vers une fonction C. En outre, en quoi la copie du code source de la fonction pourrait-elle être différente de from math import sqrt?
Dan Getz
Ce ne serait pas le cas, je l'ai dit juste pour préciser exactement quelle était la différence en appelant les deux fonctions.
PyGuy