Python'da hangisi daha hızlı: x **. 5 veya math.sqrt (x)?


188

Bunu bir süredir merak ediyorum. Başlığın dediği gibi, hangisi daha hızlı, gerçek işlev mi yoksa sadece yarı güce mi yükseliyor?

GÜNCELLEME

Bu erken bir optimizasyon meselesi değildir. Bu sadece temel kodun gerçekten nasıl çalıştığı sorusudur. Python kodunun nasıl çalıştığı teorisi nedir?

Guido van Rossum'a bir e-posta gönderdim, çünkü bu yöntemlerdeki farklılıkları gerçekten bilmek istedim.

E-postam:

Python'da karekök yapmanın en az 3 yolu vardır: math.sqrt, '**' operatörü ve pow (x, .5). Sadece bunların her birinin uygulanmasındaki farklılıkları merak ediyorum. Verimlilik söz konusu olduğunda hangisi daha iyi?

Onun yanıtı:

pow ve ** eşdeğerdir; math.sqrt karmaşık sayılar için çalışmaz ve C sqrt () işlevine bağlantılar. Hangisinin daha hızlı olduğu hakkında hiçbir fikrim yok ...


81
Guido'nun e-postaya yanıt vermesi harika.
Evan Fosmark

3
Evan, bir cevap aldığım için şaşırdım
Hayır

11
Bunun kötü bir soru olduğunu düşünmüyorum. Örneğin, x * x, x ** 2'den tam 10 kat daha hızlıdır. Okunabilirlik bu durumda bir çözümdür, neden hızlı yolu yapmıyorsunuz?
TM.

12
Casey, "erken optimizasyon" konusunda seninleyim. :) Sorunuz bana erken optimizasyon gibi görünmüyor: Varyantlardan herhangi birinin kodunuzu kırma riski yoktur. Daha çok math.sqrt () üzerinden pow () seçtiğinizde ne yaptığınızı (yürütme süresi açısından) daha iyi bilmek meselesidir.
Eric O Lebigot

8
Bu, erken optimizasyon değildir, aksine erken kötüleşmeden kaçınılmaktadır (ref. No. 28, C ++ kodlama standartları, A.Alexandrescu). Eğer math.sqrt(olduğu gibi) daha iyi duruma rutindir ve açıkça niyet daha fazla ifade, her zaman tercih edilmelidir x**.5. Ne yazdığınızı bilmek erken optimizasyon değildir ve daha hızlı ve daha fazla kod netliği sağlayan alternatifi seçer. Öyleyse, diğer alternatifleri neden seçeceğiniz konusunda eşit derecede tartışmanız gerekir.
swalog

Yanıtlar:


90

math.sqrt(x)önemli ölçüde daha hızlıdır x**0.5.

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

10 döngü, döngü başına en iyi 3: 156 ms

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

10 döngü, en iyi 3: döngü başına 91,1 ms

Python 3.6.9 ( dizüstü bilgisayar ) kullanımı.


Şimdi codepad.org üzerinde 3 kez çalıştırın ve her üç kez de () b () çok daha hızlıydı.
Jeremy Ruten

10
Standart timeit modülü arkadaşınızdır. Uygulama süresinin ölçülmesi söz konusu olduğunda yaygın tuzaklardan kaçınır!
Eric O Lebigot

1
Komut dosyanızın sonuçları: zoltan @ host: ~ $ python2.5 p.py 0.183226 saniye aldı 0.155829 saniye aldı zoltan @ host: ~ $ python2.4 p.py 0.181142 saniye aldı 0.153742 saniye aldı zoltan @ host: ~ $ python2.6 p.py 0.157436 saniye sürdü 0.093905 saniye sürdü Hedef sistem: Ubuntu Linux CPU: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2.80GHz Gördüğünüz gibi farklı sonuçlar aldım. Buna göre cevabınız jenerik değil.
zoli2k

2
Codepad harika bir hizmet, ancak zamanlama performansı için korkunç, yani sunucunun belirli bir anda ne kadar meşgul olacağını kim bilebilir. Her koşu potansiyel olarak çok farklı sonuçlar verebilir
adamJLev

1
Linux'ta py32, py31, py30, py27, py26, pypy, jython, py25, py24 tercümanlar için x **. 5'e karşı sqrt (x) performans karşılaştırmasını ekledim. gist.github.com/783011
jfs

19
  • optimizasyonun ilk kuralı: yapma
  • İkinci kural: bunu yapmayın henüz

İşte bazı zamanlamalar (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

Bu test bunun x**.5biraz daha hızlı olduğunu gösteriyor sqrt(x).

Python 3.0 için sonuç tam tersidir:

$ \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)her zaman x**.5başka bir makineden daha hızlıdır (Ubuntu, Python 2.6 ve 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

Gerçekten kaç kare kök gösteriyorsunuz? Python'da 3D grafik motoru yazmaya mı çalışıyorsunuz? Değilse, neden okunması kolay kod üzerinden şifreli kod ile gitmelisiniz? Zaman farkı, hemen hemen her uygulamada fark edebileceğimden daha az olacaktır. Gerçekten sorunuzu ortaya koymak istemiyorum, ancak erken optimizasyonla biraz fazla ilerlediğiniz görülüyor.


16
Gerçekten erken bir optimizasyon yaptığımı hissetmiyorum. Daha çok, ortalama olarak daha hızlı olacak 2 farklı yöntemden karar vermek basit bir sorudur.
Hayır

2
Kibbee: Bu kesinlikle geçerli bir soru, ama yığınınızı Stack Overflow ile ilgili soruların sayısıyla paylaşıyorum, bu da askerin her türlü erken optimizasyonu gerçekleştirdiğini ima ediyor. Kesinlikle her dil için sorulan soruların büyük bir yüzdesi.
Eli Courtwright

2
Math.sqrt (x) okunması x ** 0,5'ten daha kolay mı? Bence her ikisi de açıkça kare kök ... en azından yine de python aşina iseniz. ** Python'a aşina olmadığınız için ** "şifreli" gibi standart python operatörlerini çağırmayın.
TM.

5
** işlecinin şifreli olduğunu sanmıyorum. Kare kökünü matematiklerine uymayanlara biraz şifreli hale getirmenin bir yöntemi olarak üs 0.5'e bir şey yükseltmek olduğunu düşünüyorum.
Kibbee

13
Ya Python'da 3D bir motor yapıyorsa?
Chris Burt-Brown

9

Bu mikro-ölçütlerde, matematik ad alanında math.sqrtaramak için biraz zaman geçmesi nedeniyle daha yavaş olacaktır sqrt. İle biraz geliştirebilirsiniz

 from math import sqrt

O zaman bile, timeit boyunca birkaç varyasyon çalıştırmak, hafif bir (% 4-5) performans avantajı gösterir. x**.5

İlginçtir,

 import math
 sqrt = math.sqrt

çok daha az hızlandı, çok az istatistiksel öneme sahip, hızdaki% 1'lik farkın içinde.


Kibbee'yi tekrar edeceğim ve bunun muhtemelen erken bir optimizasyon olduğunu söyleyeceğim.


7

Python 2.6'da (float).__pow__() fonksiyon C pow()fonksiyonunu, math.sqrt()fonksiyonlar ise C fonksiyonunu kullanır.sqrt() fonksiyonunu .

Glibc derleyicisinin uygulanması pow(x,y)oldukça karmaşıktır ve çeşitli istisnai durumlar için iyi optimize edilmiştir. Örneğin, C çağrılması işlevi pow(x,0.5)çağırır sqrt().

Kullanım hızı .**veya math.sqrtC işlevleri çevresinde kullanılan sarmalayıcıların neden olduğu fark ve hız , sistemde kullanılan optimizasyon bayraklarına / C derleyicisine bağlıdır.

Düzenle:

İşte makinemdeki Claudiu algoritmasının sonuçları. Farklı sonuçlar aldım:

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

Değeri için (Jim'in cevabına bakınız). Makinemde, python 2.5 çalıştıran:

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

Claudiu'nun kodunu kullanarak, makinemde "matematik içe aktarma sqrt'tan" x ** olsa bile. 5 daha hızlıdır, ancak psyco.full () sqrt (x) kullanmak çok daha hızlı olur, en az% 200


3

Büyük olasılıkla math.sqrt (x), çünkü kare köklenme için optimize edilmiştir.

Deneyler size aradığınız cevabı verecektir.


3

Birisi Quake 3'ten "hızlı Newton-Raphson karekökü" hakkında yorum yaptı ... Bunu ctypes ile uyguladım, ancak yerel versiyonlara kıyasla süper yavaş. Birkaç optimizasyonu ve alternatif uygulamaları deneyeceğim.

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

Yapıyı kullanan başka bir yöntem, ctypes versiyonundan yaklaşık 3.6 kat daha hızlı çıkıyor, ancak yine de C'nin 1 / 10'u.

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

Claudiu'nun sonuçları benimkinden farklı. Eski bir P4 2.4Ghz makinesinde Ubuntu üzerinde Python 2.6 kullanıyorum ... İşte sonuçlarım:

>>> 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 benim için sürekli daha hızlı ... ŞİMDİ Codepad.org ŞİMDİ bile, yerel bağlamda sqrt'ın daha hızlı olduğunu kabul ediyor gibi görünüyor ( http://codepad.org/6trzcM3j ). Kod takımı şu anda Python 2.5 çalıştırıyor gibi görünüyor. Belki de Claudiu ilk cevap verdiğinde 2,4 veya daha yaşlı kullanıyorlardı?

Aslında arg (i) yerine math.sqrt (i) kullanarak bile sqrt için daha iyi zamanlar alıyorum. Bu durumda timeit2 () makinemde 0,53 ile 0,55 saniye arasında sürdü, bu da timeit1'den 0,56-0,60 rakamlarından daha iyi.

Modern Python'da math.sqrt kullanın ve bunu somevar = math.sqrt veya matematik içe aktarma sqrt ile yerel bağlama getirin.


1

Optimize edilecek Pythonic şey okunabilirliktir. Bunun için,sqrt işlevin en iyisidir. Bunu söyledikten sonra, performansı yine de araştıralım.

Claudiu'nun Python 3 kodunu güncelledim ve hesaplamaları optimize etmeyi imkansız hale getirdim (iyi bir Python derleyicisinin gelecekte yapabileceği bir şey):

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

Sonuçlar değişiklik gösterir, ancak örnek çıktı:

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

Kendin dene.


0

Son zamanlarda çözdüğüm sorun SQRMINSUM büyük bir veri kümesinde tekrar tekrar kare kök hesaplamak gerektirir. Benim en eski 2 gönderimler tarihin diğer optimizasyonlar yaptık önce, böylelikle PYPY içinde 0.51s için 3.74s Çalışma zamanını azaltarak, sqrt () ile ** 0,5 değiştirerek yalnızca farklıdır. Bu, Claudiu'nun ölçtüğü zaten% 400'lük büyük iyileşmenin neredeyse iki katı.


0

Tabii ki, eğer birebir değişimlerle uğraşıyorsa ve sabit bir değere ihtiyaç duyuyorsa, Python çalışma zamanı, işleçlerle yazılmışsa, derleme zamanında değeri önceden hesaplayabilir - bu durumda her sürümü profillemeye gerek yoktur:

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

Daha hızlı olacak olan şey, math.py'ye girip "sqrt" işlevini programınıza kopyalamanızdır. Programınızın math.py dosyasını bulup açması, aradığınız işlevi bulması ve daha sonra programınıza geri getirmesi zaman alır. Bu işlev "arama" adımlarında bile daha hızlıysa, işlevin kendisinin çok hızlı olması gerekir. Muhtemelen zamanınızı yarıya indirecek. Özetle:

  1. Math.py sayfasına git
  2. "Sqrt" fonksiyonunu bulun
  3. Kopyala
  4. Fonksiyonu programınıza sqrt finder olarak yapıştırın.
  5. Zamanla.

1
Bu işe yaramaz; bkz. stackoverflow.com/q/18857355/3004881 . Ayrıca orijinal sorudaki C işlevine bir bağlantı olduğunu belirten alıntıya dikkat edin. Ayrıca, işlevin kaynak kodunu kopyalamaktan nasıl farklı olabilir from math import sqrt?
Dan Getz

İki işlevi çağırırken farkın tam olarak ne olduğunu netleştirmek için söylemedim.
PyGuy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.