Что быстрее в Python: x**.5 или math.sqrt(x)?
Я задавался вопросом об этом в течение некоторого времени. Как следует из названия, что быстрее, фактическая функция или просто повышение до половины мощности?
ОБНОВИТЬ
Это не вопрос преждевременной оптимизации. Это просто вопрос того, как на самом деле работает базовый код. Какова теория того, как работает код Python?
Я послал Гвидо ван Россуму электронное письмо, потому что я действительно хотел знать различия в этих методах.
Моя электронная почта:
Есть как минимум 3 способа сделать квадратный корень в Python: math.sqrt, оператор '**' и pow(x,.5). Мне просто любопытно относительно различий в реализации каждого из них. Что касается эффективности, что лучше?
Его ответ:
pow и ** эквивалентны; math.sqrt не работает для комплексных чисел и ссылается на функцию C sqrt(). Что касается того, который быстрее, я понятия не имею...
16 ответов
Согласно комментариям, я обновил код:
import time
import math
def timeit1():
s = time.time()
for i in xrange(750000):
z=i**.5
print "Took %f seconds" % (time.time() - s)
def timeit2(arg=math.sqrt):
s = time.time()
for i in xrange(750000):
z=arg(i)
print "Took %f seconds" % (time.time() - s)
timeit1()
timeit2()
Теперь math.sqrt
Функция находится непосредственно в локальном аргументе, то есть имеет самый быстрый поиск.
ОБНОВЛЕНИЕ: версия Python, кажется, имеет значение здесь. Раньше я думал, что timeit1
будет быстрее, так как, когда python анализирует "i**.5", он синтаксически знает, какой метод вызывать (__pow__
или некоторый вариант), так что не нужно проходить через поиск, что math.sqrt
вариант делает. Но я могу ошибаться
Python 2.5: 0.191000 против 0.224000
Python 2.6: 0.195000 против 0.139000
Также психо, кажется, имеет дело с math.sqrt
лучше:
Python 2.5 + Psyco 2.0: 0.109000 против 0.043000
Python 2.6 + Psyco 2.0: 0.128000 против 0.067000
| Interpreter | x**.5, | sqrt, | sqrt faster, % |
| | seconds | seconds | |
|----------------+---------+---------+----------------|
| Python 3.2rc1+ | 0.32 | 0.27 | 19 |
| Python 3.1.2 | 0.136 | 0.088 | 55 |
| Python 3.0.1 | 0.155 | 0.102 | 52 |
| Python 2.7 | 0.132 | 0.079 | 67 |
| Python 2.6.6 | 0.121 | 0.075 | 61 |
| PyPy 1.4.1 | 0.083 | 0.0159 | 422 |
| Jython 2.5.1 | 0.132 | 0.22 | -40 |
| Python 2.5.5 | 0.129 | 0.125 | 3 |
| Python 2.4.6 | 0.131 | 0.123 | 7 |
#+TBLFM: $4=100*($2-$3)/$3;%.0f
Таблица результатов производится на машине:
$ uname -vms
Linux #42-Ubuntu SMP Thu Dec 2 02:41:37 UTC 2010 x86_64
$ cat /proc/cpuinfo | grep 'model name' | head -1
model name : Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz
Чтобы воспроизвести результаты:
- получить источник:
git clone git://gist.github.com/783011.git gist-783011
- устанавливать
tox
:pip install tox
- бежать
tox
из каталога сtox.ini
файл.
- первое правило оптимизации: не делай этого
- второе правило: пока не делай
Вот некоторые моменты (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
В этих микро-бенчмарках math.sqrt будет работать медленнее из-за небольшого времени, которое требуется для поиска sqrt в пространстве имен math. Вы можете немного улучшить его
from math import sqrt
Тем не менее, даже несмотря на то, что несколько изменений во времени показывают небольшое преимущество (4-5%) для "x**.5"
интересно, занимаюсь
import math
sqrt = math.sqrt
ускорили его еще больше, с точностью до 1% разницы в скорости, с очень небольшой статистической значимостью.
Я повторю Кибби и скажу, что это, вероятно, преждевременная оптимизация.
Сколько квадратных корней вы действительно исполняете? Вы пытаетесь написать движок трехмерной графики на Python? Если нет, то зачем идти с кодом, который является загадочным по сравнению с кодом, который легко читать? Разница во времени будет меньше, чем кто-либо мог заметить практически в любом приложении, которое я мог предвидеть. Я действительно не хочу задавать ваш вопрос, но кажется, что вы слишком далеко зашли с преждевременной оптимизацией.
В Python 2.6 (float).__pow__()
функция использует C pow()
функция и math.sqrt()
функции использует C sqrt()
функция.
В компиляторе 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
Скорее всего, math.sqrt(x), потому что он оптимизирован для квадратного корня.
Тесты предоставят вам ответ, который вы ищете.
Используя код Клаудиу, на моей машине даже с "from math import sqrt" x**.5 быстрее, но использование psyco.full() sqrt(x) становится намного быстрее, по крайней мере, на 200%
Для чего это стоит (см. Ответ Джима). На моей машине работает 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
Кто-то прокомментировал "быстрый квадратный корень Ньютона-Рафсона" из 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
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
Привет!Я только что создал профиль Stack Exchange, чтобы участвовать в этом разговоре! То, что я делаю, может показаться тривиальным, но выслушайте меня один раз, прежде чем судить:
Условия эксперимента:
- В автономном режиме (нет проблем с интернет-компилятором)
- Сохранение состояния моей системы как можно более стабильным
- За одну попытку протестировать все 3 функции
Я выполнил 3 цикла по 5 итераций в каждом для каждой функции, указанной в исходном вопросе. И я вычислил квадратный корень для целых чисел от 0 до 10^8 в каждом цикле.
Вот результаты:Затраченное время:
pow(x, 0.5)
Примечание. С точностью до двузначного числа секунд более 10^8 неотрицательных целых чисел.
Скриншот выходов:Выходы
Мой вывод:
Я чувствую, что электронная почта Гвидо хорошо оправдывает эти сроки. Рассмотрим следующие утверждения:
- "связывается с C и не поддерживает комплексные числа"
- "и эквивалентны"
Таким образом, мы можем подразумевать, что
И очень примечательно,
Дайте мне знать, если ваше мнение отличается от моего в этом выводе!
Код:
import time
import math
print("x**0.5 : ")
for _ in range(5):
start = time.time()
for i in range(int(1e8)):
i**0.5
end = time.time()
print(end-start)
print("math.sqrt(x) : ")
for _ in range(5):
start = time.time()
for i in range(int(1e8)):
math.sqrt(i)
end = time.time()
print(end-start)
print("pow(x,0.5) : ")
for _ in range(5):
start = time.time()
for i in range(int(1e8)):
pow(i,0.5)
end = time.time()
print(end-start)
Результаты Клаудиу отличаются от моих. Я использую 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 NOW, похоже, согласен с тем, что 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.
Конечно, если вы имеете дело с литералами и вам нужно постоянное значение, среда выполнения 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
Проблема SQRMINSUM, которую я недавно решил, требует многократного вычисления квадратного корня для большого набора данных. 2 самых старых представления в моей истории, до того, как я провел другие оптимизации, отличаются только заменой **0,5 на sqrt(), тем самым сокращая время выполнения с 3,74 до 0,51 в PyPy. Это почти вдвое больше и без того значительного улучшения на 400%, которое измерял Клавдиу.
Возможно, вы захотите сравнить и быстрый квадратный корень Ньютона-Рафсона. Не нужно много, чтобы перейти на Python.
Что будет еще быстрее, если вы зайдете в math.py и скопируете функцию "sqrt" в свою программу. Вашей программе требуется время, чтобы найти math.py, затем открыть его, найти нужную функцию и затем вернуть ее в свою программу. Если эта функция быстрее даже с шагами "поиска", то сама функция должна быть ужасно быстрой. Возможно сократит ваше время пополам. В итоге:
- Перейти к math.py
- Найти функцию "sqrt"
- Скопируйте это
- Вставьте функцию в вашу программу как средство поиска sqrt.
- Время это.