Що швидше в Python: x **. 5 або math.sqrt (x)?


187

Я це цікавився деякий час. Як говориться в заголовку, яка швидша, фактична функція або просто підвищення до половини потужності?

ОНОВЛЕННЯ

Це не питання передчасної оптимізації. Це просто питання про те, як насправді працює базовий код. Яка теорія того, як працює код Python?

Я надіслав Гідо ван Россуму електронний лист, тому що мені дуже хотілося знати відмінності в цих методах.

Мій електронний лист:

Існує щонайменше 3 способи зробити квадратний корінь у Python: math.sqrt, оператор '**' та pow (x, .5). Мені просто цікаво щодо відмінностей у здійсненні кожного з них. Що стосується ефективності, яка краща?

Його відповідь:

pow і ** еквівалентні; math.sqrt не працює для складних чисел і посилається на функцію C sqrt (). Щодо того, хто швидший, я поняття не маю ...


81
Це дивовижно, що Гуїдо відповідає на електронну пошту.
Еван Фосмарк

3
Еван, я був здивований , я отримав відповідь
Неа

11
Я не вважаю це поганим питанням. Наприклад, x * x - це в 10 разів швидше, ніж x ** 2. Читання в цій ситуації є киданням, так чому б не зробити швидкий шлях?
ТМ.

12
Кейсі, я з вами з приводу "передчасної оптимізації". :) Ваше питання для мене не схоже на передчасну оптимізацію: немає ризику, що якийсь із варіантів порушить ваш код. Це більше питання, щоб краще знати, що ви робите (з точки зору часу виконання), коли ви вибираєте pow () над math.sqrt ().
Ерік О Лебігот

8
Це не передчасна оптимізація, а скоріше уникнення передчасної песимізації (посилання №28, стандарти кодування C ++, А.Александреску). Якщо math.sqrtє більш оптимізованим рутинним (як є) і виражає наміри чіткіше, його завжди слід віддавати перевагу x**.5. Це не передчасна оптимізація, щоб знати, що ви пишете, і обрали альтернативу, яка є швидшою та забезпечує більшу чіткість коду. Якщо це так, вам потрібно однаково добре аргументувати, чому ви обрали б інші варіанти.
swalog

Відповіді:


89

math.sqrt(x)значно швидше, ніж x**0.5.

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

10 петель, найкраще 3: 156 мс на цикл

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

10 петель, найкраще 3: 91,1 мс на цикл

Використання Python 3.6.9 ( зошит ).


Зараз я запускав його 3 рази на codepad.org, і всі три рази a () був набагато швидшим, ніж b ().
Джеремі Рутен

10
Стандартний модуль timeit - ваш друг. Це дозволяє уникнути поширених підводних каменів, коли справа стосується вимірювання часу виконання!
Ерік О Лебігот

1
Ось результати вашого сценарію: zoltan @ host: ~ $ python2.5 p.py взяв 0.183226 секунд Зайняв 0.155829 секунд zoltan @ хост: ~ $ python2.4 p.py взяв 0.181142 секунди взяв 0.153742 секунди zoltan @ host: ~ $ python2.6 p.py Набрав 0.157436 секунд Набрав 0.093905 секунд Цільова система: Ubuntu Linux CPU: Intel (R) Core (TM) 2 Duo CPU T9600 @ 2,80 ГГц Як ви бачите, я отримав різні результати. Відповідно до цього ваша відповідь не є загальною.
zoli2k

2
Codepad - чудовий сервіс, але жахливий для виконання часу, я маю на увазі, хто знає, наскільки зайнятий сервер буде в даний момент. Кожен запуск потенційно може дати дуже різні результати
adamJLev

1
Я додав порівняння продуктивності інтерпретаторів x **. 5 проти sqrt (x) для py32, py31, py30, py27, py26, pypy, jython, py25, py24 для інтерпретаторів Linux. gist.github.com/783011
jfs

19
  • перше правило оптимізації: не робіть цього
  • Друге правило: не робіть цього , поки

Ось деякі таймінги (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

Цей тест показує, що x**.5це трохи швидше, ніжsqrt(x) .

Для Python 3.0 результат зворотний:

$ \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)завжди швидше, ніж x**.5на іншій машині (Ubuntu, Python 2.6 та 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

Скільки квадратних коренів ви насправді виконуєте? Ви намагаєтесь написати якийсь 3D графічний движок на Python? Якщо ні, то навіщо йти з кодом, який кричущий над кодом, який легко читати? Різниця в часі була б меншою, ніж хто-небудь міг помітити майже в будь-якій програмі, яку я міг передбачити. Я справді не хочу ставити своє запитання, але здається, що ви занадто далеко зайшли з передчасною оптимізацією.


16
я не відчуваю, що роблю передчасну оптимізацію. Це більш просте питання вирішення двох різних методів, які в середньому будуть швидшими.
Нема

2
Kibbee: це, безумовно, правильне запитання, але я поділяю ваше враження щодо кількості запитань щодо переповнення стека, які означають, що запитувач виконує всі види передчасної оптимізації. Це, безумовно, великий відсоток запитань щодо кожної мови.
Eli Courtwright

2
Чи математично.sqrt (x) читати простіше, ніж x ** 0,5? Я думаю, вони обидва, очевидно, квадратного кореня ... принаймні, якщо ви все-таки знайомі з python. Не називайте стандартних операторів python на зразок ** "криптовалютними" лише тому, що ви не знайомі з python.
ТМ.

5
Я не думаю, що оператор ** є дурним. Я думаю, що підняти щось до показника 0,5 як метод отримання квадратного кореня бути трохи загадковим для тих, хто не продовжує свою математику.
Kibbee

13
Що робити, якщо він робить 3D-двигун в Python?
Кріс Берт-Браун

9

У цих мікро-орієнтирах math.sqrtбуде повільніше, через невеликий час, який потрібен для пошуку sqrtв математичному просторі імен. Ви можете трохи покращити його

 from math import sqrt

Навіть тоді, виконуючи кілька варіацій через timeit, виявіть невелику (4-5%) перевагу продуктивності для x**.5

Цікаво, що роблять

 import math
 sqrt = math.sqrt

пришвидшив його ще більше, з точністю до 1% різниці у швидкості, що має дуже малу статистичну значимість.


Я повторю Кіббі і скажу, що це, мабуть, передчасна оптимізація.


7

У python 2.6 (float).__pow__() функція використовує функцію C, pow()а math.sqrt()функції використовує Csqrt() функцію.

У компіляторі glibc реалізація pow(x,y)досить складна і вона добре оптимізована для різних виняткових випадків. Наприклад, виклик C pow(x,0.5)просто викликає sqrt()функцію.

Різниця в швидкості використання .**або math.sqrtвикликана обгортками, що використовуються навколо функцій C, і швидкість сильно залежить від оптимізаційних прапорів / компілятора C, використовуваних у системі.

Редагувати:

Ось результати алгоритму Клавдіу на моїй машині. Я отримав різні результати:

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

Для чого це варто (див. Відповідь Джима). На моїй машині працює пітон 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

використовуючи код Клауді, на моїй машині навіть з "з математики імпорту sqrt" x **. 5 швидше, але використання psyco.full () sqrt (x) стає набагато швидше, принаймні на 200%


3

Швидше за все, math.sqrt (x), оскільки він оптимізований для прямого вкорінення.

Орієнтири дадуть вам відповідь, яку ви шукаєте.


3

Хтось прокоментував "швидкий квадратний корінь Ньютона-Рафсона" від Quake 3 ... Я реалізував це за допомогою ctypes, але це дуже повільно порівняно з рідними версіями. Я спробую кілька оптимізацій та альтернативних реалізацій.

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

Ось ще один метод, що використовує структуру, виходить приблизно в 3,6 рази швидше, ніж версія ctypes, але все одно на 1/10 швидкість 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

Результати Клавдіу відрізняються від моїх. Я використовую Python 2.6 на Ubuntu на старій машині P4 2,4 ГГц ... Ось мої результати:

>>> 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 для мене стабільно швидший ... Навіть Codepad.org ЗАРАЗ, схоже, згоден, що sqrt в локальному контексті швидший ( http://codepad.org/6trzcM3j ). Схоже, в даний час Codepad працює під управлінням Python 2.5. Можливо, вони вживали 2.4 або старші, коли Клавдіу вперше відповів?

Насправді, навіть використовуючи math.sqrt (i) замість arg (i), я все-таки отримую кращі часи для sqrt. У цьому випадку timeit2 () зайняв від моєї машини від 0,53 до 0,55 секунди, що все-таки краще, ніж показники 0,56-0,60 від timeit1.

Я б сказав, що на сучасному Python використовуйте math.sqrt і обов'язково приведіть його до локального контексту, або з somevar = math.sqrt, або з sqport імпорту математики.


1

Пітонічна річ, яку слід оптимізувати - це читабельність. Для цього я думаю, що явне використанняsqrt найкраще функцію. Сказавши це, давайте все-таки вивчимо ефективність.

Я оновив код Claudiu для Python 3, а також унеможливив оптимізацію подальших обчислень (щось хороше може зробити компілятор Python у майбутньому):

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

Результати різняться, але вибір вибірки:

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

Спробуйте самі.


0

Проблема SQRMINSUM, яку я нещодавно вирішила, вимагає багаторазового обчислення квадратного кореня на великому наборі даних. Найдавніші 2 подання в моїй історії , перш ніж я робив інші оптимізації, відрізняються виключно тим, що замінили ** 0,5 на sqrt (), тим самим скоротивши час роботи з 3,74 до 0,51 в PyPy. Це майже вдвічі більше, ніж вже 400-мільйонне покращення, яке вимірював Клавдіу.


0

Звичайно, якщо хтось має справу з літералами і потребує постійного значення, час виконання Python може попередньо обчислити значення під час компіляції, якщо воно записане з операторами - не потрібно профілювати кожну версію в цьому випадку:

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

Що ще швидше, якщо ви зайшли в math.py і скопіювали функцію "sqrt" у свою програму. Щоб ваша програма потребувала часу, щоб знайти math.py, потім відкрити її, знайти потрібну функцію, а потім повернути її до вашої програми. Якщо ця функція швидша навіть з кроками "пошуку", сама функція повинна бути надзвичайно швидкою. Можливо, скоротить ваш час навпіл. У резюме:

  1. Перейдіть на math.py
  2. Знайдіть функцію "sqrt"
  3. Скопіюйте
  4. Вставити функцію у вашу програму як шукач sqrt.
  5. Час це.

1
Це не спрацює; див. stackoverflow.com/q/18857355/3004881 . Також зверніть увагу на цитату в оригінальному запитанні, в якій сказано, що це посилання на функцію C. Крім того, чим може відрізнятися копіювання вихідного коду функції from math import sqrt?
Dan Getz

Нічого б я не сказав, я просто сказав, щоб точно зрозуміти, в чому різниця полягає у виклику двох функцій.
PyGuy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.