Điều này có vẻ là do phép nhân số lượng nhỏ được tối ưu hóa trong CPython 3.5, theo cách mà dịch chuyển trái với số lượng nhỏ thì không. Các dịch chuyển trái tích cực luôn tạo ra một đối tượng số nguyên lớn hơn để lưu trữ kết quả, như là một phần của phép tính, trong khi đối với phép nhân của loại bạn đã sử dụng trong thử nghiệm của mình, một tối ưu hóa đặc biệt sẽ tránh điều này và tạo ra một đối tượng số nguyên có kích thước chính xác. Điều này có thể được nhìn thấy trong mã nguồn của việc triển khai số nguyên của Python .
Vì các số nguyên trong Python là chính xác tùy ý, chúng được lưu trữ dưới dạng các mảng "chữ số" nguyên, với giới hạn về số bit trên mỗi chữ số nguyên. Vì vậy, trong trường hợp chung, các hoạt động liên quan đến số nguyên không phải là các hoạt động đơn lẻ, mà thay vào đó cần xử lý trường hợp có nhiều "chữ số". Trong pyport.h , giới hạn bit này được xác định là 30 bit trên nền tảng 64 bit, hoặc 15 bit khác. (Tôi sẽ chỉ gọi số 30 này từ đây để giữ cho lời giải thích đơn giản. Nhưng lưu ý rằng nếu bạn đang sử dụng Python được biên dịch cho 32 bit, kết quả điểm chuẩn của bạn sẽ phụ thuộc vào việcx
có nhỏ hơn 32.768 hay không.)
Khi đầu vào và đầu ra của một hoạt động nằm trong giới hạn 30 bit này, hoạt động có thể được xử lý theo cách tối ưu hóa thay vì cách chung. Bắt đầu thực hiện phép nhân số nguyên như sau:
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
}
Vì vậy, khi nhân hai số nguyên trong đó mỗi số trùng với một chữ số 30 bit, điều này được thực hiện dưới dạng phép nhân trực tiếp bởi trình thông dịch CPython, thay vì làm việc với các số nguyên dưới dạng mảng. ( MEDIUM_VALUE()
được gọi trên một đối tượng số nguyên dương chỉ đơn giản nhận được chữ số 30 bit đầu tiên của nó.) Nếu kết quả khớp với một chữ số 30 bit duy nhất,PyLong_FromLongLong()
sẽ nhận thấy điều này trong một số lượng hoạt động tương đối nhỏ và tạo một đối tượng số nguyên một chữ số để lưu trữ nó
Ngược lại, các ca làm việc bên trái không được tối ưu hóa theo cách này và mỗi ca làm việc bên trái liên quan đến số nguyên được dịch chuyển dưới dạng một mảng. Cụ thể, nếu bạn xem mã nguồn long_lshift()
, trong trường hợp dịch chuyển trái nhỏ nhưng dương, một đối tượng số nguyên 2 chữ số luôn được tạo, nếu chỉ để rút ngắn độ dài thành 1 sau: (nhận xét của tôi trong /*** ***/
)
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);
/*** ... ***/
}
Bộ phận nguyên
Bạn đã không hỏi về hiệu suất kém hơn của phân chia số nguyên so với ca phải, bởi vì điều đó phù hợp với kỳ vọng của bạn (và của tôi). Nhưng việc chia một số dương nhỏ cho một số dương nhỏ khác cũng không được tối ưu hóa như các phép nhân nhỏ. Mỗi //
tính toán cả thương và phần còn lại sử dụng hàm long_divrem()
. Phần còn lại này được tính cho một ước số nhỏ với phép nhân và được lưu trữ trong một đối tượng số nguyên mới được phân bổ , trong trường hợp này sẽ bị loại bỏ ngay lập tức.
x
đâu?