Почему x ** 4.0 быстрее, чем x ** 4 в Python 3?

Почему x**4.0 быстрее, чем x**4? Я использую CPython 3.5.2.

$ python -m timeit "for x in range(100):" " x**4.0"
  10000 loops, best of 3: 24.2 usec per loop

$ python -m timeit "for x in range(100):" " x**4"
  10000 loops, best of 3: 30.6 usec per loop

Я попытался изменить силу, которую я поднял, чтобы посмотреть, как она действует, и, например, если я подниму x до 10 или 16, то прыгаю с 30 на 35, но если я поднимаюсь на 10.0 как float, он просто перемещается вокруг 24.1 ~ 4.

Я думаю, что это имеет какое-то отношение к конвертации float и возможностям 2, возможно, но я действительно не знаю.

Я заметил, что в обоих случаях мощности 2 быстрее, я думаю, так как эти вычисления являются более родными/легкими для интерпретатора/компьютера. Но все же, с поплавками он почти не двигается. 2.0 => 24.1~4 & 128.0 => 24.1~4 , но 2 => 29 & 128 => 62


TigerhawkT3 указал, что это не происходит за пределами цикла. Я проверил, и ситуация возникает только (из того, что я видел), когда появляется base. Есть идеи об этом?

Ответ 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. Другие реализации могут выполняться по-разному.

Ответ 2

Если мы посмотрим на байт-код, мы увидим, что выражения абсолютно идентичны. Единственное отличие - это тип константы, который будет аргументом BINARY_POWER. Поэтому это, безусловно, связано с тем, что int преобразуется в число с плавающей запятой по строке.

>>> def func(n):
...    return n**4
... 
>>> def func1(n):
...    return n**4.0
... 
>>> from dis import dis
>>> dis(func)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4)
              6 BINARY_POWER
              7 RETURN_VALUE
>>> dis(func1)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (4.0)
              6 BINARY_POWER
              7 RETURN_VALUE

Обновление: взглянем на Objects/abstract.c в исходном коде CPython:

PyObject *
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
{
    return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()");
}

PyNumber_Power вызывает ternary_op, который слишком длинный для вставки здесь, поэтому здесь ссылка.

Он вызывает nb_power слот x, передавая y в качестве аргумента.

Наконец, в float_pow() в строке 686 Objects/floatobject.c мы видим, что аргументы преобразуются в C double прямо перед фактическая операция:

static PyObject *
float_pow(PyObject *v, PyObject *w, PyObject *z)
{
    double iv, iw, ix;
    int negate_result = 0;

    if ((PyObject *)z != Py_None) {
        PyErr_SetString(PyExc_TypeError, "pow() 3rd argument not "
            "allowed unless all arguments are integers");
        return NULL;
    }

    CONVERT_TO_DOUBLE(v, iv);
    CONVERT_TO_DOUBLE(w, iw);
    ...

Ответ 3

Поскольку один правильный, другой - приближение.

>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625