Почему x**4.0
быстрее, чем x**4
в Python 3 * ?
int
Объекты Python 3 - это полноценный объект, разработанный для поддержки произвольного размера; в связи с этим они обрабатываются как таковые на уровне C (смотрите, как все переменные объявляются как PyLongObject *
type in long_pow
). Это также делает их возведение в степень более хитрым и утомительным, поскольку вам нужно поиграться с ob_digit
массивом, который он использует для представления его значения для его выполнения. ( Источник для смелых. - См .: Понимание распределения памяти для больших целых чисел в Python для получения дополнительной информации о PyLongObject
s.)
float
Объекты Python , напротив, могут быть преобразованы в double
тип C (с помощью PyFloat_AsDouble
), а операции могут выполняться с использованием этих собственных типов . Это здорово , потому что, после проверки соответствующих реберных случаев, это позволяет Python , чтобы использовать платформыpow
( C - х pow
, то есть ) для обработки фактического возведения в степени:
/* Now iv and iw are finite, iw is nonzero, and iv is
* positive and not equal to 1.0. We finally allow
* the platform pow to step in and do the rest.
*/
errno = 0;
PyFPE_START_PROTECT("pow", return NULL)
ix = pow(iv, iw);
где iv
и iw
наши оригинальные PyFloatObject
s как C double
s.
Для чего это стоит: Python 2.7.13
для меня является фактором 2~3
быстрее и показывает обратное поведение.
Предыдущий факт также объясняет несоответствие между Python 2 и 3, поэтому я подумал, что я тоже рассмотрю этот комментарий, потому что он интересен.
В Python 2 вы используете старый int
объект, который отличается от int
объекта в Python 3 (все int
объекты в 3.x имеют PyLongObject
тип). В Python 2 есть различие, которое зависит от значения объекта (или, если вы используете суффикс L/l
):
# Python 2
type(30) # <type 'int'>
type(30L) # <type 'long'>
<type 'int'>
Вы видите здесь делает то же самое float
с делать , он получает благополучно превращается в C , long
когда экспоненцирование выполняется на нем ( int_pow
также намекает на компилятор поместил их в реестр , если он может сделать это, так что может сделать разницу) :
static PyObject *
int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
{
register long iv, iw, iz=0, ix, temp, prev;
/* Snipped for brevity */
это позволяет получить хороший прирост скорости.
Чтобы увидеть, насколько медлительны <type 'long'>
s по сравнению с <type 'int'>
s, если вы обернули x
имя в long
вызове в Python 2 (по сути, заставляя его использовать long_pow
как в Python 3), прирост скорости исчезает:
# <type 'int'>
(python2) ➜ python -m timeit "for x in range(1000):" " x**2"
10000 loops, best of 3: 116 usec per loop
# <type 'long'>
(python2) ➜ python -m timeit "for x in range(1000):" " long(x)**2"
100 loops, best of 3: 2.12 msec per loop
Обратите внимание, что, хотя один фрагмент преобразовывает значение int
в, long
а другой - нет (на что указывает @pydsinger), это приведение не является движущей силой замедления. Реализация long_pow
есть. (Время заявления только с, long(x)
чтобы увидеть).
[...] это не происходит вне цикла. [...] Есть идеи по этому поводу?
Это оптимизатор глазка CPython, складывающий константы для вас. Вы получаете одинаковые точные значения времени в любом случае, так как нет фактических вычислений, чтобы найти результат возведения в степень, только загрузка значений:
dis.dis(compile('4 ** 4', '', 'exec'))
1 0 LOAD_CONST 2 (256)
3 POP_TOP
4 LOAD_CONST 1 (None)
7 RETURN_VALUE
Идентичный байт-код генерируется для '4 ** 4.'
с той лишь разницей, что LOAD_CONST
загружает float 256.0
вместо int 256
:
dis.dis(compile('4 ** 4.', '', 'exec'))
1 0 LOAD_CONST 3 (256.0)
2 POP_TOP
4 LOAD_CONST 2 (None)
6 RETURN_VALUE
Так что времена совпадают.
* Все вышеперечисленное относится исключительно к CPython, эталонной реализации Python. Другие реализации могут работать по-другому.