Ini tampaknya karena penggandaan angka kecil dioptimalkan dalam CPython 3.5, dengan cara yang tidak bergeser oleh angka kecil tidak. Pergeseran kiri positif selalu membuat objek bilangan bulat yang lebih besar untuk menyimpan hasilnya, sebagai bagian dari perhitungan, sedangkan untuk perkalian dari jenis yang Anda gunakan dalam pengujian Anda, optimasi khusus menghindari hal ini dan membuat objek bilangan bulat dengan ukuran yang benar. Ini dapat dilihat pada kode sumber implementasi integer Python .
Karena integer dengan Python presisi-arbitrary, mereka disimpan sebagai array dari integer "digit", dengan batas jumlah bit per digit integer. Jadi dalam kasus umum, operasi yang melibatkan bilangan bulat bukan operasi tunggal, melainkan harus menangani kasus beberapa "digit". Di pyport.h , batas bit ini ini didefinisikan sebagai 30 bit pada platform 64-bit, atau 15 bit sebaliknya. (Saya hanya akan memanggil 30 ini dari sini untuk menjaga penjelasannya sederhana. Tetapi perhatikan bahwa jika Anda menggunakan Python yang dikompilasi untuk 32-bit, hasil patokan Anda akan tergantung pada apakah x
kurang dari 32.768 atau tidak.)
Ketika input dan output operasi tetap dalam batas 30-bit ini, operasi dapat ditangani dengan cara yang dioptimalkan daripada cara umum. Awal implementasi multiplikasi integer adalah sebagai berikut:
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
}
Jadi ketika mengalikan dua bilangan bulat di mana masing-masing cocok dalam digit 30-bit, ini dilakukan sebagai perkalian langsung oleh juru bahasa CPython, alih-alih bekerja dengan bilangan bulat sebagai array. ( MEDIUM_VALUE()
dipanggil pada objek integer positif hanya mendapatkan digit 30-bit pertamanya.) Jika hasilnya cocok dengan digit 30-bit tunggal,PyLong_FromLongLong()
akan melihat ini dalam jumlah operasi yang relatif kecil, dan membuat objek integer digit tunggal untuk menyimpan Itu.
Sebaliknya, shift kiri tidak dioptimalkan dengan cara ini, dan setiap shift kiri berkaitan dengan integer yang digeser sebagai array. Secara khusus, jika Anda melihat kode sumber untuk long_lshift()
, dalam kasus shift kiri kecil tapi positif, objek integer 2 digit selalu dibuat, jika hanya untuk panjangnya dipotong menjadi 1 nanti: (komentar saya di /*** ***/
)
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);
/*** ... ***/
}
Divisi integer
Anda tidak bertanya tentang kinerja divisi bilangan bulat yang lebih buruk dibandingkan dengan shift yang tepat, karena itu sesuai dengan harapan Anda (dan saya). Tetapi membagi angka positif kecil dengan angka positif kecil lainnya juga tidak dioptimalkan seperti perkalian kecil. Setiap //
menghitung hasil bagi dan sisanya menggunakan fungsi long_divrem()
. Sisa ini dihitung untuk pembagi kecil dengan perkalian , dan disimpan dalam objek integer yang baru dialokasikan , yang dalam situasi ini segera dibuang.
x
?