Что быстрее в Python: x **. 5 или math.sqrt (x)?

188

Мне было интересно это в течение некоторого времени. Как следует из названия, что быстрее, фактическая функция или просто повышение до половины мощности?

ОБНОВИТЬ

Это не вопрос преждевременной оптимизации. Это просто вопрос того, как на самом деле работает базовый код. Какова теория того, как работает код 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 ++, A.Alexandrescu). Если math.sqrtэто более оптимизированная процедура (как она есть) и она выражает намерение более четко, ее всегда следует отдавать предпочтение x**.5. Это не преждевременная оптимизация, чтобы знать, что вы пишете, и выбрал альтернативу, которая быстрее и обеспечивает большую ясность кода. Если это так, вам нужно одинаково хорошо спорить, почему вы бы выбрали другие альтернативы.
Сулог

Ответы:

90

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 @ host: ~ $ python2,4 p.py Занимало 0,181142 секунд Занимало 0,153742 секунды zoltan @ host: ~ $ python2.6 p.py Потребовалось 0,157436 секунд. Потребовалось 0,093905 секунд. Целевая система: Ubuntu Linux Процессор: Intel® Core® TM Duo 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
JFS
источник
10

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

Kibbee
источник
16
я действительно не чувствую, что делаю преждевременную оптимизацию. Это более простой вопрос выбора из 2 разных методов, который в среднем будет быстрее.
Нет
2
Кибби: это, безусловно, правильный вопрос, но я разделяю ваше беспокойство по поводу количества вопросов о переполнении стека, которые подразумевают, что запрашивающий выполняет все виды преждевременной оптимизации. Это определенно большой процент вопросов, задаваемых для каждого языка.
Эли Кортрайт
2
Легче ли читать math.sqrt (x), чем x ** 0.5? Я думаю, что они оба явно квадратный корень ... по крайней мере, если вы все равно знакомы с питоном. Не называйте стандартные операторы Python, такие как **, «загадочными» только потому, что вы не знакомы с Python.
ТМ.
5
Я не думаю, что оператор ** является загадочным. Я думаю, что возведение чего-либо к показателю 0.5 в качестве метода получения квадратного корня должно быть немного загадочным для тех, кто не успевает за своей математикой.
Кибби
13
Что если он делает 3D движок на Python?
Крис Бурт-Браун
9

В этих микро-бенчмарках math.sqrtбудет медленнее, из-за небольшого времени, которое требуется для поиска sqrtв математическом пространстве имен. Вы можете немного улучшить его

 from math import sqrt

Тем не менее, несмотря на несколько изменений во времени, они показывают небольшое (4-5%) преимущество в производительности для x**.5

Интересно, занимаюсь

 import math
 sqrt = math.sqrt

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


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

JimB
источник
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
zoli2k
источник
4

Для чего это стоит (см. Ответ Джима). На моей машине работает 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
источник
4

используя код Клаудиу, на моей машине даже с "from math import sqrt" x **. 5 быстрее, но использование psyco.full () sqrt (x) становится намного быстрее, по крайней мере, на 200%

Нет
источник
3

Скорее всего, math.sqrt (x), потому что он оптимизирован для квадратного корня.

Тесты предоставят вам ответ, который вы ищете.

strager
источник
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

Вот еще один метод, использующий struct, который примерно в 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
lunixbochs
источник
1

Результаты Клавдия отличаются от моих. Я использую Python 2.6 на Ubuntu на старой машине P4 2.4Ghz ... Вот мои результаты:

>>> 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, либо с помощью команды math import sqrt.

bobpaul
источник
1

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

Я обновил код Клаудиу для 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

Попробуй сам.

hkBst
источник
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
jsbueno
источник
-3

Что будет еще быстрее, если вы зайдете в math.py и скопируете функцию «sqrt» в свою программу. Вашей программе требуется время, чтобы найти math.py, затем открыть его, найти нужную функцию и затем вернуть ее в свою программу. Если эта функция быстрее даже с шагами «поиска», то сама функция должна быть ужасно быстрой. Вероятно, сократит ваше время пополам. В итоге:

  1. Перейти к math.py
  2. Найти функцию "sqrt"
  3. Скопируйте это
  4. Вставьте функцию в вашу программу как sqrt finder.
  5. Время это.
PyGuy
источник
1
Это не сработает; см. stackoverflow.com/q/18857355/3004881 . Также обратите внимание на цитату из исходного вопроса, в которой говорится, что это ссылка на функцию C. Кроме того, чем может отличаться копирование исходного кода функции from math import sqrt?
Дэн Гетц
Я бы сказал, что это не совсем ясно, в чем именно заключается разница в вызове двух функций.
PyGuy