Ответ 1
Почему
x**4.0
быстрее, чемx**4
в Python 3 *?
Объекты Python 3 int
- это полноценный объект, предназначенный для поддержки произвольного размера; из-за этого они обрабатываются как таковые на уровне C (см., как все переменные объявлены как PyLongObject *
type в long_pow
). Это также делает их возведение в степень намного более сложным и утомительным, так как вам нужно играть с массивом ob_digit
, который он использует, чтобы представить его значение для его выполнения. (Источник для храбрых. - См.: Общие сведения о распределении памяти для больших целых чисел в Python больше для PyLongObject
s.)
Объекты Python float
, напротив, могут быть преобразованы в тип C double
(используя 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
как 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
когда выполняется возведение в степень it (The 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'>
по сравнению с <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. Другие реализации могут выполняться по-разному.