Ciò sembra essere dovuto al fatto che la moltiplicazione di piccoli numeri è ottimizzata in CPython 3.5, in modo tale che non lo siano gli spostamenti a sinistra per piccoli numeri. I turni a sinistra positivi creano sempre un oggetto intero più grande per memorizzare il risultato, come parte del calcolo, mentre per le moltiplicazioni dell'ordinamento utilizzato nel test, una speciale ottimizzazione evita questo e crea un oggetto intero della dimensione corretta. Questo può essere visto nel codice sorgente dell'implementazione intera di Python .
Poiché gli interi in Python sono di precisione arbitraria, vengono memorizzati come matrici di "cifre" intere, con un limite al numero di bit per cifra intera. Quindi, nel caso generale, le operazioni che coinvolgono numeri interi non sono singole operazioni, ma devono invece gestire il caso di più "cifre". In pyport.h , questo limite di bit è definito come 30 bit su piattaforma a 64 bit o altrimenti 15 bit. (Chiamerò questo 30 da qui in poi per mantenere la spiegazione semplice. Ma nota che se stavi usando Python compilato per 32-bit, il risultato del tuo benchmark dipenderebbe sex
fosse inferiore a 32.768 o meno.)
Quando gli ingressi e le uscite di un'operazione rimangono entro questo limite di 30 bit, l'operazione può essere gestita in modo ottimizzato anziché in modo generale. L'inizio del moltiplicazione dei numeri interi è il seguente:
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
}
Quindi, quando si moltiplicano due numeri interi in cui ciascuno si inserisce in una cifra di 30 bit, questo viene fatto come una moltiplicazione diretta dall'interprete CPython, invece di lavorare con i numeri interi come array. (MEDIUM_VALUE()
chiamato su un oggetto intero positivo ottiene semplicemente la sua prima cifra di 30 bit.) Se il risultato si adatta a una singola cifra di 30 bit, PyLong_FromLongLong()
lo noterà in un numero relativamente piccolo di operazioni e creerà un oggetto intero a una cifra da memorizzare esso.
Al contrario, gli spostamenti a sinistra non sono ottimizzati in questo modo e ogni spostamento a sinistra si occupa dell'intero spostamento come array. In particolare, se si osserva il codice sorgente per long_lshift()
, nel caso di uno spostamento sinistro piccolo ma positivo, viene sempre creato un oggetto intero a 2 cifre, se non altro per avere la sua lunghezza troncata a 1 in seguito: (i miei commenti in /*** ***/
)
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);
/*** ... ***/
}
Divisione intera
Non hai chiesto le prestazioni peggiori della divisione dei piani interi rispetto ai turni giusti, perché soddisfano le tue (e mie) aspettative. Ma anche la divisione di un piccolo numero positivo per un altro piccolo numero positivo non è ottimizzata come le piccole moltiplicazioni. Ognuno //
calcola sia il quoziente che il resto usando la funzione long_divrem()
. Questo resto viene calcolato per un piccolo divisore con una moltiplicazione e viene archiviato in un oggetto intero appena allocato , che in questa situazione viene immediatamente scartato.
x
?