Чому x ** 4.0 швидше, ніж x ** 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 зазначив, що це не відбувається поза петлею. Я перевірив, і ситуація виникає (з того, що я бачила), коли база піднімається. Будь-яка ідея про це?


11
Для чого це варто: Python 2.7.13 для мене є коефіцієнтом 2 ~ 3 швидшим і показує зворотну поведінку: цілий показник швидше, ніж показник плаваючої точки.

4
@Evert yup, я отримав 14 Usec для x**4.0та 3,9 for x**4.
dabadaba

Відповіді:


161

Чому x**4.0 швидше, ніж x**4у Python 3 * ?

intОб'єкти Python 3 - це повноцінний об'єкт, призначений для підтримки довільного розміру; завдяки цьому факту вони обробляються як такі на рівні С (дивіться, як усі змінні оголошуються як PyLongObject *тип в 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 разів. (Не один із низових людей, однак)
Грайфер,

3
@ mbomb007 усунення <type 'long'>типу в Python 3, ймовірно, пояснюється зусиллями, що докладаються для спрощення мови. Якщо ви можете мати один тип для представлення цілих чисел, це більш керовано, ніж два (і турбуватися про перетворення одного з іншого в інший при необхідності, користувачі плутаються тощо). Підвищення швидкості є другорядним. Розділ про ПЕП 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 об’єктів / 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);
    ...

1
@ Жан-ФрансуаFabre Я вважаю, що це пов'язано з постійним складанням.
Димитріс Фасаракіс Хілліард

2
Я думаю, що сенс того, що відбувається конверсія, і вони не розглядаються по-різному вниз за лінією "звичайно" - це трохи розтягнення без джерела.
miradulo

1
@Mitch - Тим більше, що в цьому конкретному коді немає різниці у часі виконання цих двох операцій. Різниця виникає лише з циклу ОП. Ця відповідь підходить до висновків.
TigerhawkT3

2
Чому ви дивитесь лише тоді, float_powколи це навіть не працює для повільного випадку?
user2357112 підтримує Моніку

2
@ TigerhawkT3: 4**4і 4**4.0отримуйте постійну складку. Це абсолютно окремий ефект.
user2357112 підтримує Моніку

-1

Оскільки одне правильно, інше - наближення.

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

Я не знаю, чому цей прихильник знизився, але я це зробив, тому що ця відповідь не відповідає на запитання. Просто тому, що щось правильно, жодним чином не означає, що це швидше чи повільніше. Один повільніше, ніж інший, оскільки один може працювати з типами C, а інший повинен працювати з об’єктами Python.
Димитріс Фасаракіс Хілліард

1
Дякую за пояснення. Ну, я дійсно вважав, що очевидно, що швидше обчислити лише наближення числа до 12 або близько цифр, ніж обчислити їх усі точно. Зрештою, єдина причина, чому ми використовуємо наближення, полягає в тому, що вони швидше обчислюють, правда?
Веки
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.