Bunun nedeni, CPython 3.5'te küçük sayıların çoğalmasının, küçük sayılara göre sol kaymaların olmayacağı şekilde optimize edilmesidir. Pozitif sola kaydırmalar, hesaplamanın bir parçası olarak sonucu saklamak için her zaman daha büyük bir tamsayı nesnesi yaratırken, testinizde kullandığınız sıralamanın çarpımı için özel bir optimizasyon bunu önler ve doğru boyutta bir tamsayı nesnesi oluşturur. Bu, Python'un tamsayı uygulamasının kaynak kodunda görülebilir .
Python'daki tamsayılar hassas olduğundan, tamsayı basamağı başına bit sayısında bir sınır ile tamsayı "basamak" dizileri olarak saklanır. Bu nedenle, genel durumda, tamsayıları içeren işlemler tek işlem değildir, bunun yerine birden çok "basamak" durumunda işlem yapması gerekir. Olarak pyport.h bu bit sınır olarak tanımlanmaktadır aksi 64 bit platformunda 30 bit veya 15 bit. (Açıklamayı basit tutmak için buradan 30'u arayacağım. Ancak 32 bit için derlenmiş Python kullanıyorsanız, karşılaştırmanızın sonucunun x
32.768'den az olup olmamasına bağlı olacağını unutmayın.)
Bir işlemin giriş ve çıkışları bu 30 bitlik sınırın içinde kaldığında, işlem genel yol yerine optimize edilmiş bir şekilde işlenebilir. Tamsayı çarpım uygulamasının başlangıcı aşağıdaki gibidir:
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
}
Dolayısıyla, her birinin 30 bitlik bir basamağa sığdığı iki tamsayıyı çarparken, bu, tamsayılarla diziler olarak çalışmak yerine, CPython yorumlayıcısı tarafından doğrudan çarpma olarak yapılır. ( MEDIUM_VALUE()
pozitif bir tamsayı nesnesinde çağrıldığında ilk 30 bit basamağını alır.) Sonuç tek bir 30 bit basamağa sığarsa, PyLong_FromLongLong()
bunu nispeten az sayıda işlemde fark eder ve saklamak için tek basamaklı bir tam sayı nesnesi oluşturur o.
Buna karşılık, sola kaydırma bu şekilde optimize edilmez ve her sola kaydırma, tamsayı dizi olarak kaydırılmakla ilgilenir. Özellikle, kaynak koduna bakarsanız, long_lshift()
küçük ama pozitif bir sola kaydırma durumunda, yalnızca uzunluğunun daha sonra 1'e kesilmesi durumunda her zaman 2 basamaklı bir tam sayı nesnesi oluşturulur: (yorumlarımda /*** ***/
)
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);
/*** ... ***/
}
Tam sayı bölümü
Tam vardiya bölümünün doğru vardiyalara kıyasla daha kötü performansını sormadınız, çünkü bu sizin (ve benim) beklentilerinize uyuyor. Ancak küçük bir pozitif sayıyı başka bir küçük pozitif sayıya bölmek de küçük çarpmalar kadar optimize edilmez. Her biri fonksiyonu kullanarak //
hem bölümü hem de geri kalanını hesaplar long_divrem()
. Bu kalan, çarpma ile küçük bir bölen için hesaplanır ve bu durumda hemen atılan yeni tahsis edilmiş bir tam sayı nesnesinde saklanır .
x
?