Це, мабуть, тому, що множення малих чисел оптимізовано в CPython 3.5 таким чином, що ліві зрушення на невеликі числа не є. Позитивні зсуви ліворуч завжди створюють більший цілий об'єкт для збереження результату як частини обчислення, тоді як для множень типу, який ви використовували у своєму тесті, спеціальна оптимізація уникає цього і створює цілий об'єкт правильного розміру. Це можна побачити у вихідному коді цілочисельної реалізації Python .
Оскільки цілі числа в Python є довільною точністю, вони зберігаються у вигляді масивів цілих «цифр», з обмеженням на кількість бітів на цілочисельну цифру. Тож у загальному випадку операції, що включають цілі числа, не є одиничними операціями, а натомість потрібно обробляти випадок декількох "цифр". У pyport.h цей бітовий ліміт визначається як 30 біт на 64-бітній платформі, або 15 біт в іншому випадку. (Я просто зателефоную цьому 30 звідси, щоб пояснення було простим. Але зауважте, що якщо ви використовували Python, скомпільований для 32-розрядних, результат вашого орієнтиру залежав би від того, чи x
було б менше 32,768 чи ні.)
Коли входи та виходи операції залишаються в межах 30-бітової межі, операцію можна обробляти оптимізованим способом замість загального. Початок реалізації цілого множення полягає в наступному:
static PyObject *
long_mul(PyLongObject *a, PyLongObject *b)
{
PyLongObject *z;
CHECK_BINOP(a, b);
/* fast path for single-digit multiplication */
if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {
stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b);
#ifdef HAVE_LONG_LONG
return PyLong_FromLongLong((PY_LONG_LONG)v);
#else
/* if we don't have long long then we're almost certainly
using 15-bit digits, so v will fit in a long. In the
unlikely event that we're using 30-bit digits on a platform
without long long, a large v will just cause us to fall
through to the general multiplication code below. */
if (v >= LONG_MIN && v <= LONG_MAX)
return PyLong_FromLong((long)v);
#endif
}
Отже, при множенні двох цілих чисел, де кожне вписується в 30-бітну цифру, це робиться як пряме множення інтерпретатором CPython, а не робота з цілими числами як масиви. (MEDIUM_VALUE()
виклик позитивного цілого об'єкта просто отримує свою першу 30-розрядну цифру.) Якщо результат вписується в одну 30-бітну цифру, PyLong_FromLongLong()
це помітить у відносно невеликій кількості операцій і створить одноцифровий цілий цілий об'єкт для зберігання це.
На відміну від цього, ліві зрушення не оптимізовані таким чином, і кожен зсув лівого зсуву має справу з цілим числом, яке зміщується як масив. Зокрема, якщо ви подивитесь на вихідний код для long_lshift()
, у випадку невеликого, але позитивного лівого зсуву, 2-значний цілочисельний об'єкт завжди створюється, якщо тільки його довжина буде обрізана до 1 пізніше: (мої коментарі у /*** ***/
)
static PyObject *
long_lshift(PyObject *v, PyObject *w)
{
/*** ... ***/
wordshift = shiftby / PyLong_SHIFT; /*** zero for small w ***/
remshift = shiftby - wordshift * PyLong_SHIFT; /*** w for small w ***/
oldsize = Py_ABS(Py_SIZE(a)); /*** 1 for small v > 0 ***/
newsize = oldsize + wordshift;
if (remshift)
++newsize; /*** here newsize becomes at least 2 for w > 0, v > 0 ***/
z = _PyLong_New(newsize);
/*** ... ***/
}
Ціле ділення
Ви не запитували про гірші показники цілого поділу поверху порівняно з правильними зрушеннями, оскільки це відповідало вашим (і моїм) очікуванням. Але ділення невеликого додатного числа на інше невелике додатне число також не настільки оптимізоване, як мале множення. Кожен //
обчислює як коефіцієнт, так і залишок, використовуючи функцію long_divrem()
. Цей залишок обчислюється для малого дільника з множенням і зберігається в щойно виділеному цілому об'єкті , який у цій ситуації негайно відкидається.
x
?