Was ist in Python schneller: x **. 5 oder math.sqrt (x)?

187

Ich habe mich schon seit einiger Zeit gefragt. Wie der Titel schon sagt, was ist schneller, die eigentliche Funktion oder einfach auf die halbe Potenz zu erhöhen?

AKTUALISIEREN

Dies ist keine Frage der vorzeitigen Optimierung. Dies ist lediglich eine Frage der tatsächlichen Funktionsweise des zugrunde liegenden Codes. Wie funktioniert Python-Code?

Ich habe Guido van Rossum eine E-Mail geschickt, weil ich wirklich die Unterschiede in diesen Methoden wissen wollte.

Meine E-Mail:

Es gibt mindestens drei Möglichkeiten, eine Quadratwurzel in Python zu erstellen: math.sqrt, der Operator '**' und pow (x, .5). Ich bin nur neugierig auf die Unterschiede in der Implementierung von jedem dieser. Was ist besser, wenn es um Effizienz geht?

Seine Antwort:

pow und ** sind äquivalent; math.sqrt funktioniert nicht für komplexe Zahlen und verknüpft mit der Funktion C sqrt (). Welcher schneller ist, weiß ich nicht ...

Nee
quelle
81
Das ist großartig, der Guido antwortet auf E-Mails.
Evan Fosmark
3
Evan, ich war überrascht, dass ich eine Antwort bekam
Nein,
11
Ich denke nicht, dass dies eine schlechte Frage ist. Zum Beispiel ist x * x zehnmal schneller als x ** 2. Die Lesbarkeit ist in dieser Situation ein Problem. Warum also nicht den schnellen Weg gehen?
TM.
12
Casey, ich bin mit Ihnen in der Sache "Vorzeitige Optimierung". :) Ihre Frage sieht für mich nicht nach vorzeitiger Optimierung aus: Es besteht kein Risiko, dass eine der Varianten Ihren Code beschädigt. Es geht eher darum, besser zu wissen, was Sie tun (in Bezug auf die Ausführungszeit), wenn Sie pow () anstelle von math.sqrt () wählen.
Eric O Lebigot
8
Dies ist keine vorzeitige Optimierung, sondern die Vermeidung einer vorzeitigen Pessimisierung (Ref. Nr. 28, C ++ - Codierungsstandards, A.Alexandrescu). Wenn math.sqrtes sich um eine optimierte Routine handelt (wie sie ist) und die Absicht klarer zum Ausdruck bringt, sollte sie immer vorgezogen werden x**.5. Es ist keine vorzeitige Optimierung, zu wissen, was Sie schreiben, und die Alternative zu wählen, die schneller ist und mehr Codeklarheit bietet. Wenn ja, müssen Sie gleich gut argumentieren, warum Sie die anderen Alternativen gewählt haben.
Swalog

Antworten:

89

math.sqrt(x)ist deutlich schneller als x**0.5.

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

10 Schleifen, am besten 3: 156 ms pro Schleife

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

10 Schleifen, am besten 3: 91,1 ms pro Schleife

Verwenden von Python 3.6.9 ( Notizbuch ).

Claudiu
quelle
Ich habe es jetzt dreimal auf codepad.org ausgeführt und alle drei Male war a () viel schneller als b ().
Jeremy Ruten
10
Das Standard-Timeit-Modul ist dein Freund. Es vermeidet häufige Fallstricke bei der Messung der Ausführungszeit!
Eric O Lebigot
1
Hier sind die Ergebnisse Ihres Skripts: zoltan @ host: ~ $ python2.5 p.py Dauerte 0.183226 Sekunden Dauerte 0.155829 Sekunden zoltan @ host: ~ $ python2.4 p.py Dauerte 0.181142 Sekunden Dauerte 0.153742 Sekunden zoltan @ host: ~ $ python2.6 p.py Dauerte 0.157436 Sekunden Dauerte 0.093905 Sekunden Zielsystem: Ubuntu Linux CPU: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2.80GHz Wie Sie sehen, habe ich unterschiedliche Ergebnisse erzielt. Demnach ist Ihre Antwort nicht generisch.
zoli2k
2
Codepad ist ein großartiger Dienst, aber für die Timing-Leistung schrecklich. Ich meine, wer weiß, wie beschäftigt der Server in einem bestimmten Moment sein wird. Jeder Lauf kann möglicherweise zu sehr unterschiedlichen Ergebnissen führen
adamJLev
1
Ich habe einen Leistungsvergleich von x **. 5 mit sqrt (x) für py32-, py31-, py30-, py27-, py26-, pypy-, jython-, py25- und py24-Interpreter unter Linux hinzugefügt. gist.github.com/783011
jfs
19
  • erste Regel der Optimierung: Tu es nicht
  • zweite Regel: Mach es noch nicht

Hier sind einige Timings (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

Dieser Test zeigt, dass x**.5das etwas schneller ist als sqrt(x).

Für Python 3.0 ist das Ergebnis das Gegenteil:

$ \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)ist immer schneller als x**.5auf einem anderen Computer (Ubuntu, Python 2.6 und 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
quelle
10

Wie viele Quadratwurzeln spielen Sie wirklich? Versuchen Sie, eine 3D-Grafik-Engine in Python zu schreiben? Wenn nicht, warum dann mit kryptischem Code über leicht lesbaren Code gehen? Der Zeitunterschied wäre geringer, als irgendjemand in so gut wie jeder Anwendung, die ich vorhersehen könnte, bemerken könnte. Ich möchte Ihre Frage wirklich nicht aufschreiben, aber es scheint, dass Sie mit vorzeitiger Optimierung etwas zu weit gehen.

Kibbee
quelle
16
Ich habe nicht wirklich das Gefühl, dass ich eine vorzeitige Optimierung mache. Es ist eher eine einfache Frage, sich für zwei verschiedene Methoden zu entscheiden, die im Durchschnitt schneller sind.
Nein,
2
Kibbee: Es ist definitiv eine gültige Frage, aber ich teile Ihre Bestürzung über die Anzahl der Fragen zum Stapelüberlauf, die implizieren, dass der Fragesteller alle Arten vorzeitiger Optimierung durchführt. Es ist definitiv ein großer Prozentsatz der Fragen, die für jede Sprache gestellt werden.
Eli Courtwright
2
Ist math.sqrt (x) leichter zu lesen als x ** 0.5? Ich denke, sie sind beide ziemlich offensichtlich Quadratwurzel ... zumindest, wenn Sie sowieso mit Python vertraut sind. Nennen Sie Standard-Python-Operatoren wie ** nicht "kryptisch", nur weil Sie mit Python nicht vertraut sind.
TM.
5
Ich denke nicht, dass der Operator ** kryptisch ist. Ich denke, dass es etwas ist, etwas auf den Exponenten 0,5 zu erhöhen, um die Quadratwurzel für diejenigen, die nicht mit ihrer Mathematik Schritt halten, ein wenig kryptisch zu machen.
Kibbee
13
Was ist, wenn er eine 3D-Engine in Python erstellt?
Chris Burt-Brown
9

In diesen Mikro-Benchmarks math.sqrtwird es langsamer sein, da das Nachschlagen sqrtim mathematischen Namespace nur wenig Zeit in Anspruch nimmt . Sie können es mit leicht verbessern

 from math import sqrt

Selbst dann, wenn Sie einige Variationen durch die Zeit laufen lassen, zeigen Sie einen leichten (4-5%) Leistungsvorteil für x**.5

Interessanterweise tun

 import math
 sqrt = math.sqrt

beschleunigte es noch weiter, bis auf einen Geschwindigkeitsunterschied von 1%, mit sehr geringer statistischer Signifikanz.


Ich werde Kibbee wiederholen und sagen, dass dies wahrscheinlich eine vorzeitige Optimierung ist.

JimB
quelle
7

In Python 2.6 verwendet die (float).__pow__() Funktion die C- pow()Funktion und die math.sqrt()Funktionen das C.sqrt() Funktion.

Im glibc-Compiler ist die Implementierung von pow(x,y)recht komplex und für verschiedene Ausnahmefälle gut optimiert. Wenn Sie beispielsweise C aufrufen, wird pow(x,0.5)einfach die sqrt()Funktion aufgerufen.

Der Unterschied in der Geschwindigkeit der Verwendung .**oder math.sqrtwird durch die Wrapper verursacht, die für die C-Funktionen verwendet werden, und die Geschwindigkeit hängt stark von den auf dem System verwendeten Optimierungsflags / C-Compilern ab.

Bearbeiten:

Hier sind die Ergebnisse von Claudius Algorithmus auf meinem Computer. Ich habe unterschiedliche Ergebnisse erhalten:

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
quelle
4

Für das, was es wert ist (siehe Jims Antwort). Auf meinem Computer wird Python 2.5 ausgeführt:

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
quelle
4

Mit Claudius Code ist auf meinem Computer sogar mit "from math import sqrt" x **. 5 schneller, aber mit psyco.full () sqrt (x) wird es viel schneller, zumindest um 200%

Nee
quelle
3

Höchstwahrscheinlich math.sqrt (x), da es für Quadratwurzeln optimiert ist.

Benchmarks geben Ihnen die Antwort, die Sie suchen.

Strager
quelle
3

Jemand hat die "schnelle Newton-Raphson-Quadratwurzel" aus Quake 3 kommentiert ... Ich habe sie mit ctypes implementiert, aber sie ist im Vergleich zu den nativen Versionen sehr langsam. Ich werde einige Optimierungen und alternative Implementierungen ausprobieren.

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

Hier ist eine andere Methode, die struct verwendet. Sie ist etwa 3,6-mal schneller als die ctypes-Version, aber immer noch 1/10 der Geschwindigkeit von 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
quelle
1

Claudius Ergebnisse unterscheiden sich von meinen. Ich verwende Python 2.6 unter Ubuntu auf einem alten P4 2.4Ghz-Computer ... Hier sind meine Ergebnisse:

>>> 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 ist für mich durchweg schneller ... Sogar Codepad.org scheint JETZT zuzustimmen, dass sqrt im lokalen Kontext schneller ist ( http://codepad.org/6trzcM3j) ). Auf dem Codepad scheint derzeit Python 2.5 ausgeführt zu werden. Vielleicht benutzten sie 2.4 oder älter, als Claudiu das erste Mal antwortete?

Selbst wenn ich math.sqrt (i) anstelle von arg (i) verwende, bekomme ich immer noch bessere Zeiten für sqrt. In diesem Fall dauerte timeit2 () auf meinem Computer zwischen 0,53 und 0,55 Sekunden, was immer noch besser ist als die 0,56-0,60-Zahlen von timeit1.

Ich würde sagen, verwenden Sie in modernem Python math.sqrt und bringen Sie es definitiv in den lokalen Kontext, entweder mit somevar = math.sqrt oder mit aus math import sqrt.

Bobpaul
quelle
1

Die pythonische Sache, für die optimiert werden muss, ist die Lesbarkeit. Dafür denke ich explizite Verwendung dersqrt Funktion für am besten. Lassen Sie uns trotzdem die Leistung untersuchen.

Ich habe Claudius Code für Python 3 aktualisiert und es auch unmöglich gemacht, die Berechnungen zu optimieren (etwas, das ein guter Python-Compiler in Zukunft tun könnte):

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

Die Ergebnisse variieren, aber eine Beispielausgabe lautet:

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

Versuch es selber.

hkBst
quelle
0

Das Problem SQRMINSUM, das ich kürzlich gelöst habe, erfordert die wiederholte Berechnung der Quadratwurzel für einen großen Datensatz. Die ältesten 2 Einsendungen in meiner Geschichte , bevor ich andere Optimierungen vorgenommen habe, unterscheiden sich ausschließlich dadurch, dass ** 0,5 durch sqrt () ersetzt wird, wodurch die Laufzeit in PyPy von 3,74 auf 0,51 Sekunden reduziert wird. Dies ist fast das Doppelte der bereits massiven Verbesserung von 400%, die Claudiu gemessen hat.

Nadstratosfer Gonczy
quelle
0

Wenn man sich mit Literalen befasst und einen konstanten Wert benötigt, kann die Python-Laufzeit den Wert zur Kompilierungszeit vorberechnen, wenn er mit Operatoren geschrieben wird. In diesem Fall muss nicht jede Version profiliert werden:

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
quelle
-3

Noch schneller wäre es, wenn Sie in math.py die Funktion "sqrt" in Ihr Programm kopieren würden. Es dauert einige Zeit, bis Ihr Programm math.py gefunden, dann geöffnet, die gesuchte Funktion gefunden und dann wieder in Ihr Programm aufgenommen hat. Wenn diese Funktion trotz der "Such" -Schritte schneller ist, muss die Funktion selbst furchtbar schnell sein. Vermutlich wird sich Ihre Zeit halbieren. Zusammenfassend:

  1. Gehen Sie zu math.py
  2. Finden Sie die Funktion "sqrt"
  3. Kopiere es
  4. Fügen Sie die Funktion als SQL-Finder in Ihr Programm ein.
  5. Zeit es.
PyGuy
quelle
1
Das wird nicht funktionieren; Siehe stackoverflow.com/q/18857355/3004881 . Beachten Sie auch das Zitat in der ursprünglichen Frage, das besagt, dass es sich um einen Link zu einer C-Funktion handelt. Wie kann sich das Kopieren des Quellcodes der Funktion von unterscheiden from math import sqrt?
Dan Getz
Das würde ich nicht sagen, nur um genau zu verdeutlichen, was der Unterschied beim Aufrufen der beiden Funktionen ist.
PyGuy