![](/img/trans.png)
[英]Why is 2 * x * x faster than 2 * ( x * x ) in Python 3.x, for integers?
[英]Times-two faster than bit-shift, for Python 3.x integers?
我正在查看sorted_containers的來源,並驚訝地看到這一行 :
self._load, self._twice, self._half = load, load * 2, load >> 1
這里load
是一個整數。 為什么在一個地方使用位移,在另一個地方使用乘法? 似乎合理的是,位移可能比積分除以2更快,但為什么不用乘法替換乘法呢? 我對以下案例進行了基准測試:
並發現#3始終比其他替代品更快:
# self._load, self._twice, self._half = load, load * 2, load >> 1
import random
import timeit
import pandas as pd
x = random.randint(10 ** 3, 10 ** 6)
def test_naive():
a, b, c = x, 2 * x, x // 2
def test_shift():
a, b, c = x, x << 1, x >> 1
def test_mixed():
a, b, c = x, x * 2, x >> 1
def test_mixed_swapped():
a, b, c = x, x << 1, x // 2
def observe(k):
print(k)
return {
'naive': timeit.timeit(test_naive),
'shift': timeit.timeit(test_shift),
'mixed': timeit.timeit(test_mixed),
'mixed_swapped': timeit.timeit(test_mixed_swapped),
}
def get_observations():
return pd.DataFrame([observe(k) for k in range(100)])
問題:
我的考試有效嗎? 如果是這樣,為什么(乘,移)快於(移位,移位)?
我在Ubuntu 14.04上運行Python 3.5。
編輯
以上是問題的原始陳述。 Dan Getz在他的回答中提供了一個很好的解釋。
為了完整起見,下面是乘法優化不適用時較大x
示例說明。
這似乎是因為在CPython 3.5中優化了小數的乘法,其中左移小數不是。 正左移總是創建一個更大的整數對象來存儲結果,作為計算的一部分,而對於您在測試中使用的排序的乘法,一個特殊的優化避免了這種情況並創建了一個正確大小的整數對象。 這可以在Python的整數實現的源代碼中看到。
因為Python中的整數是任意精度的,所以它們存儲為整數“數字”的數組,每個整數位的位數有限制。 因此,在一般情況下,涉及整數的操作不是單個操作,而是需要處理多個“數字”的情況。 在pyport.h中 ,此位限制在64位平台上定義為 30位,否則定義為 15位。 (我將從這里開始調用這個30以保持解釋簡單。但請注意,如果你使用的是32位編譯的Python,你的基准測試結果將取決於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()
會在相對較少的操作中注意到這一點,並創建一個-digit整數對象來存儲它。
相反,左移不以這種方式優化,並且每個左移位處理作為數組移位的整數。 特別是,如果你查看long_lshift()
的源代碼,在左移小但正向的情況下,總是創建一個2位整數對象,如果只是將其長度截斷為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()
計算商和余數。 該余數是針對具有乘法的小除數計算的,並存儲在新分配的整數對象中 ,在這種情況下立即丟棄。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.