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

164

Почему 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 как поплавок, он просто движется около 24,1 ~ 4.

Я предполагаю, что это как-то связано с конвертацией с плавающей запятой и степенями 2, может быть, но я действительно не знаю.

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


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

arieljannai
источник
11
Для чего это стоит: Python 2.7.13 для меня в 2–3 раза быстрее и показывает обратное поведение: целочисленный показатель быстрее, чем показатель с плавающей запятой.
4
@ Evert yup, у меня есть 14 usec для x**4.0и 3.9 для x**4.
Дабадаба

Ответы:

161

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

intОбъекты Python 3 - это полноценный объект, разработанный для поддержки произвольного размера; в связи с этим они обрабатываются как таковые на уровне C (смотрите, как все переменные объявляются как PyLongObject *type in long_pow). Это также делает их возведение в степень более хитрым и утомительным, поскольку вам нужно поиграться с ob_digitмассивом, который он использует для представления его значения для его выполнения. ( Источник для смелых. - См .: Понимание распределения памяти для больших целых чисел в Python для получения дополнительной информации о PyLongObjects.)

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наши оригинальные PyFloatObjects как C doubles.

Для чего это стоит: 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. Другие реализации могут работать по-другому.

Димитрис Фасаракис Хиллиард
источник
Что бы это ни было, оно связано с циклом над a range, поскольку синхронизация только самой **операции не дает разницы между целыми числами и числами с плавающей точкой.
TigerhawkT3
Разница появляется только при поиске переменной ( 4**4так же быстро, как 4**4.0), и этот ответ не касается этого вообще.
TigerhawkT3
1
Но константы будут свернуты @ TigerhawkT3 ( dis(compile('4 ** 4', '', 'exec'))), поэтому время должно быть точно таким же.
Димитрис Фасаракис Хиллиард
Кажется, твои последние времена не показывают, что ты говоришь. long(x)**2.все еще быстрее, чем long(x)**2в 4-5 раз. (Не один из downvoters, хотя)
Graipher
3
@ mbomb007 устранение <type 'long'>типа в Python 3, вероятно, объясняется усилиями, предпринятыми для упрощения языка. Если у вас может быть один тип для представления целых чисел, он более управляем, чем два (и беспокоится о преобразовании из одного в другой, когда это необходимо, пользователи могут запутаться и т. Д.). Увеличение скорости является вторичным по отношению к этому. Раздел обоснования PEP 237 также предлагает более глубокое понимание.
Димитрис Фасаракис Хиллиард
25

Если мы посмотрим на байт-код, то увидим, что выражения чисто идентичны. Единственное отличие - это тип константы, который будет аргументом 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);
    ...
leovp
источник
1
@ Жан-Франсуа Фабр: Думаю, это из-за постоянного сворачивания.
Димитрис Фасаракис Хиллиард
2
Я думаю, что подразумевается, что есть преобразование, и они не обрабатываются по-разному, «наверняка», это немного растянуто без источника.
Мирадуло
1
@Mitch - особенно потому, что в этом конкретном коде нет разницы во времени выполнения этих двух операций. Разница возникает только в петле ОП. Этот ответ спешит с выводами.
TigerhawkT3
2
Почему вы смотрите только тогда, float_powкогда это не работает для медленного случая?
user2357112 поддерживает Monica
2
@ TigerhawkT3: 4**4и 4**4.0получить постоянно сложенный. Это совершенно отдельный эффект.
user2357112 поддерживает Monica
-1

Потому что одно правильно, другое - приближение.

>>> 334453647687345435634784453567231654765 ** 4.0
1.2512490121794596e+154
>>> 334453647687345435634784453567231654765 ** 4
125124901217945966595797084130108863452053981325370920366144
719991392270482919860036990488994139314813986665699000071678
41534843695972182197917378267300625
Veky
источник
Я не знаю, почему это отрицательное мнение, но я сделал, потому что этот ответ не отвечает на вопрос. То, что что-то правильно, ни в коем случае не означает, что оно быстрее или медленнее. Один медленнее другого, потому что один может работать с типами C, в то время как другой должен работать с объектами Python.
Димитрис Фасаракис Хиллиард
1
Спасибо за объяснение. Ну, я действительно думал, что было очевидно, что быстрее вычислить только приближение числа к 12 или около того цифрам, чем вычислить все из них точно. В конце концов, единственная причина, по которой мы используем приближения, состоит в том, что они быстрее вычисляются, верно?
Veky