Похоже, это связано с тем, что умножение малых чисел оптимизировано в 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()
, в случае небольшого, но положительного сдвига влево, всегда создается двузначный целочисленный объект, если только его длина будет усечена до 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
?