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