Które jest szybsze w Pythonie: x **. 5 lub math.sqrt (x)?


187

Zastanawiam się nad tym od jakiegoś czasu. Jak mówi tytuł, która jest szybsza, rzeczywista funkcja lub po prostu podniesienie do połowy mocy?

AKTUALIZACJA

Nie jest to kwestia przedwczesnej optymalizacji. Jest to po prostu pytanie o to, jak faktycznie działa podstawowy kod. Jaka jest teoria działania kodu Python?

Wysłałem Guido van Rossumowi wiadomość e-mail, ponieważ naprawdę chciałem poznać różnice w tych metodach.

Mój e-mail:

Istnieją co najmniej 3 sposoby wykonania pierwiastka kwadratowego w Pythonie: math.sqrt, operator „**” i pow (x, .5). Jestem ciekawy różnic we wdrażaniu każdego z nich. Jeśli chodzi o wydajność, która jest lepsza?

Jego odpowiedź:

pow i ** są równoważne; math.sqrt nie działa dla liczb zespolonych i łączy z funkcją C sqrt (). Co do tego, który jest szybszy, nie mam pojęcia ...


81
To niesamowite, że Guido odpowiada na e-maile.
Evan Fosmark,

3
Evan, byłem zaskoczony dostałam odpowiedź
nope

11
Nie sądzę, że to złe pytanie. Na przykład x * x jest pełne 10 razy szybsze niż x ** 2. Czytelność w tej sytuacji jest rzetelna, więc dlaczego nie zrobić tego szybko?
TM.

12
Casey, jestem z tobą w sprawie „przedwczesnej optymalizacji”. :) Twoje pytanie nie wygląda dla mnie na przedwczesną optymalizację: nie ma ryzyka, że ​​którykolwiek z wariantów złamie twój kod. Chodzi o to, aby lepiej wiedzieć, co robisz (pod względem czasu wykonania), gdy wybierzesz pow () zamiast math.sqrt ().
Eric O Lebigot

8
Nie jest to przedwczesna optymalizacja, ale raczej unikanie przedwczesnej pesymizacji (nr ref. 28, standardy kodowania C ++, A.Alexandrescu). Jeśli math.sqrtjest to bardziej zoptymalizowana rutyna (jak jest) i wyraźniej wyraża intencję, zawsze powinna być preferowana x**.5. Nie jest przedwczesna optymalizacja, aby wiedzieć, co piszesz, i wybierz alternatywę, która jest szybsza i zapewnia większą przejrzystość kodu. Jeśli tak, musisz równie dobrze argumentować, dlaczego wybrałeś inne alternatywy.
swalog

Odpowiedzi:


89

math.sqrt(x)jest znacznie szybszy niż x**0.5.

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

10 pętli, najlepiej 3: 156 ms na pętlę

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

10 pętli, najlepiej 3: 91,1 ms na pętlę

Korzystanie z Python 3.6.9 ( notebook ).


Teraz uruchomiłem go 3 razy na codepad.org i wszystkie trzy razy a () było znacznie szybsze niż b ().
Jeremy Ruten

10
Standardowy moduł timeit to twój przyjaciel. Unika typowych pułapek, jeśli chodzi o pomiar czasu wykonania!
Eric O Lebigot

1
Oto wyniki skryptu: zoltan @ host: ~ $ python2.5 p.py wziął 0,183226 sekund zajął 0,155829 sekund zoltan @ host: ~ $ python2.4 p.py wziął 0,181142 sekund wziął 0,153742 sekund zoltan @ host: ~ $ python2.6 p.py Zajęło 0,157436 sekundy Zajęło 0,093905 sekundy System docelowy: Ubuntu Linux CPU: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2,80 GHz Jak widać, otrzymałem różne wyniki. Zgodnie z tym twoja odpowiedź nie jest ogólna.
zoli2k

2
Codepad to świetna usługa, ale straszna pod względem wydajności czasowej, to znaczy, kto wie, jak zajęty będzie serwer w danym momencie. Każdy bieg może potencjalnie dać bardzo różne wyniki
adamJLev

1
Dodałem porównanie wydajności x **. 5 vs sqrt (x) dla interpreterów py32, py31, py30, py27, py26, pypy, jython, py25, py24 w systemie Linux. gist.github.com/783011
jfs

19
  • pierwsza zasada optymalizacji: nie rób tego
  • Druga zasada: nie rób tego , jeszcze

Oto niektóre czasy (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

Ten test pokazuje, że x**.5jest nieco szybszy niż sqrt(x).

W przypadku Python 3.0 wynik jest odwrotny:

$ \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)jest zawsze szybszy niż x**.5na innym komputerze (Ubuntu, Python 2.6 i 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

Ile pierwiastków kwadratowych faktycznie wykonujesz? Czy próbujesz napisać silnik graficzny 3D w Pythonie? Jeśli nie, to po co stosować kod, który jest tajemniczy, a nie łatwy do odczytania? Różnica czasu byłaby mniejsza niż ktokolwiek mógłby zauważyć w prawie każdej aplikacji, którą mogłem przewidzieć. Naprawdę nie zamierzam stawiać pytania, ale wydaje się, że posuwasz się trochę za daleko z przedwczesną optymalizacją.


16
tak naprawdę nie czuję, że przeprowadzam przedwczesną optymalizację. To bardziej proste pytanie o wybór 2 różnych metod, które średnio będą szybsze.
Nie

2
Kibbee: to zdecydowanie prawidłowe pytanie, ale podzielam twoje przerażenie liczbą pytań na temat przepełnienia stosu, które sugerują, że pytający wykonuje wszelkiego rodzaju przedwczesną optymalizację. To zdecydowanie duży odsetek pytań zadawanych w każdym języku.
Eli Courtwright

2
Czy math.sqrt (x) jest łatwiejszy do odczytania niż x ** 0,5? Myślę, że oba są oczywiście pierwiastkiem kwadratowym ... przynajmniej jeśli znasz i tak python. Nie nazywaj standardowych operatorów pythonowych, takich jak **, „tajemniczymi” tylko dlatego, że nie znasz Pythona.
TM.

5
Nie sądzę, by ** operator był tajemniczy. Myślę, że podniesienie czegoś do wykładnika 0,5 jako metody na uzyskanie pierwiastka kwadratowego jest nieco tajemnicze dla tych, którzy nie nadążają za matematyką.
Kibbee

13
Co jeśli zrobi silnik 3D w Pythonie?
Chris Burt-Brown,

9

W tych mikro-testach math.sqrtbędzie wolniejszy, ze względu na krótki czas potrzebny na wyszukiwanie sqrtw przestrzeni nazw matematyki. Możesz to nieco poprawić za pomocą

 from math import sqrt

Nawet wtedy, uruchamiając kilka odmian w czasie, wykazują niewielką (4-5%) przewagę wydajności x**.5

Co ciekawe, robi

 import math
 sqrt = math.sqrt

przyspieszyłem to jeszcze bardziej, do różnicy prędkości w granicach 1%, z bardzo małym znaczeniem statystycznym.


Powtórzę Kibbee i powiem, że jest to prawdopodobnie przedwczesna optymalizacja.


7

W Pythonie 2.6 (float).__pow__() funkcja używa funkcji C, pow()a math.sqrt()funkcje używają sqrt()funkcji C.

W kompilatorze glibc implementacja pow(x,y)jest dość złożona i jest dobrze zoptymalizowana dla różnych wyjątkowych przypadków. Na przykład wywołanie C pow(x,0.5)po prostu wywołuje sqrt()funkcję.

Różnica w prędkości używania .**lub math.sqrtjest spowodowana przez owijarki używane wokół funkcji C, a prędkość silnie zależy od flag optymalizacji / kompilatora C używanych w systemie.

Edytować:

Oto wyniki algorytmu Claudiu na mojej maszynie. Mam inne wyniki:

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

Za to, co jest warte (patrz odpowiedź Jima). Na moim komputerze działającym w Pythonie 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

używając kodu Claudiu, na mojej maszynie nawet z „z matematyki import sqrt” x **. 5 jest szybszy, ale użycie psyco.full () sqrt (x) staje się znacznie szybszy, przynajmniej o 200%


3

Najprawdopodobniej math.sqrt (x), ponieważ jest zoptymalizowany pod kątem kwadratowego rootowania.

Benchmarki zapewnią odpowiedź, której szukasz.


3

Ktoś skomentował „szybki pierwiastek kwadratowy Newtona-Raphsona” z Quake 3 ... Zaimplementowałem go z ctypami, ale jest on bardzo wolny w porównaniu z wersjami natywnymi. Wypróbuję kilka optymalizacji i alternatywnych implementacji.

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

Oto inna metoda wykorzystująca struct, wychodzi około 3,6x szybciej niż wersja ctypes, ale nadal 1/10 prędkości 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

Wyniki Claudiu różnią się od moich. Używam Python 2.6 na Ubuntu na starej maszynie P4 2.4Ghz ... Oto moje wyniki:

>>> 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 jest dla mnie konsekwentnie szybszy ... Nawet Codepad.org NOW wydaje się zgadzać, że sqrt, w kontekście lokalnym, jest szybszy ( http://codepad.org/6trzcM3j ). Wygląda na to, że Codepad obecnie działa w Pythonie 2.5. Być może używali wersji 2.4 lub starszej, kiedy Claudiu po raz pierwszy odpowiedział?

W rzeczywistości, nawet używając math.sqrt (i) zamiast arg (i), wciąż mam lepsze czasy dla sqrt. W tym przypadku timeit2 () zajął na mojej maszynie od 0,53 do 0,55 sekundy, co wciąż jest lepsze niż 0,56-0,60 z timeit1.

Powiedziałbym, że we współczesnym Pythonie użyj math.sqrt i zdecydowanie przenieś go do lokalnego kontekstu, albo somevar = math.sqrt, albo z matematycznego importu sqrt.


1

Pythoniczną rzeczą, którą należy zoptymalizować, jest czytelność. W tym celu uważam, że sqrtnajlepiej jest użyć tej funkcji. To powiedziawszy, i tak zbadajmy wydajność.

Zaktualizowałem kod Claudiu dla Pythona 3, a także uniemożliwiłem optymalizację obliczeń (coś, co dobry kompilator Pythona może zrobić w przyszłości):

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

Wyniki są różne, ale przykładowy wynik to:

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

Spróbuj sam.


0

Problem , który ostatnio rozwiązałem SQRMINSUM , wymaga wielokrotnego obliczania pierwiastka kwadratowego w dużym zbiorze danych. Najstarsze 2 zgłoszenia w mojej historii , zanim dokonałem innych optymalizacji, różnią się jedynie zastąpieniem ** 0,5 sqrt (), zmniejszając w ten sposób czas działania z 3,74 s do 0,51 s w PyPy. Jest to prawie dwukrotność już i tak ogromnej 400% poprawy, którą zmierzył Claudiu.


0

Oczywiście, jeśli mamy do czynienia z literałami i potrzebujemy stałej wartości, środowisko wykonawcze Python może wstępnie obliczyć wartość w czasie kompilacji, jeśli jest napisana z operatorami - w tym przypadku nie trzeba profilować każdej wersji:

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

Byłoby jeszcze szybciej, gdybyś wszedł na math.py i skopiował funkcję „sqrt” do swojego programu. Twój program potrzebuje czasu na znalezienie pliku math.py, a następnie jego otwarcie, znalezienie funkcji, której szukasz, a następnie przywrócenie jej do programu. Jeśli ta funkcja jest szybsza nawet po wykonaniu kroków „wyszukiwania”, to sama funkcja musi być strasznie szybka. Prawdopodobnie skrócisz czas o połowę. W podsumowaniu:

  1. Przejdź do math.py
  2. Znajdź funkcję „sqrt”
  3. Skopiuj to
  4. Wklej funkcję do swojego programu jako wyszukiwarkę sqrt.
  5. Czas to.

1
To nie zadziała; patrz stackoverflow.com/q/18857355/3004881 . Zwróć także uwagę na cytat z oryginalnego pytania, który mówi, że jest to link do funkcji C. W jaki sposób kopiowanie kodu źródłowego funkcji może się różnić od from math import sqrt?
Dan Getz

Nie byłoby tak, powiedziałem to, aby wyjaśnić dokładnie, na czym polega różnica w wywoływaniu obu funkcji.
PyGuy,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.