簡體   English   中英

對於Python 3.x整數,時間比比特移位快兩倍?

[英]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更快,但為什么不用乘法替換乘法呢? 我對以下案例進行了基准測試:

  1. (次,分)
  2. (輪班,輪班)
  3. (次,班次)
  4. (轉移,分裂)

並發現#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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM