पायथन 3.x पूर्णांक के लिए बिट-शिफ्ट की तुलना में टाइम्स-दो तेजी से है?


150

मैं 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 पर पायथन 3.5 चलाता हूं।

संपादित करें

ऊपर प्रश्न का मूल कथन है। दान गेट्ज़ अपने उत्तर में एक उत्कृष्ट व्याख्या प्रदान करता है।

पूर्णता की खातिर, xजब गुणा अनुकूलन लागू नहीं होते हैं, तो बड़े के लिए नमूना चित्र यहां दिए गए हैं ।

यहाँ छवि विवरण दर्ज करें यहाँ छवि विवरण दर्ज करें


3
आपने कहां परिभाषित किया x?
जेबरनार्डो

3
मैं वास्तव में यह देखना चाहूंगा कि क्या छोटे एंडियन / बड़े एंडियन का उपयोग करने में कोई अंतर है। वास्तव में अच्छा सवाल btw!
LiGhTx117

1
@ LiGhTx117 मैं उम्मीद करूंगा कि जब तक xयह बहुत बड़ा नहीं है , जब तक कि यह स्मृति में संग्रहीत नहीं है, क्योंकि यह बहुत बड़ा है, संचालन के लिए असंबंधित है ?
डैन गेट्ज़

1
मैं उत्सुक हूँ, 2 से विभाजित होने के बजाय 0.5 से गुणा करने के बारे में क्या? मिप्स असेंबली प्रोग्रामिंग के साथ पिछले अनुभव से, विभाजन आम तौर पर वैसे भी कई गुना अधिक होता है। (यह विभाजन के बजाय बिट शिफ्टिंग की प्राथमिकता को स्पष्ट करेगा)
सायसे

2
@ पता है कि यह चल बिंदु पर परिवर्तित होगा। उम्मीद है कि पूर्णांक तल विभाजन फ्लोटिंग पॉइंट के माध्यम से एक गोल यात्रा की तुलना में तेज होगा।
डैन गेट्ज़

जवाबों:


155

ऐसा इसलिए लगता है क्योंकि छोटी संख्याओं का गुणा CPython 3.5 में अनुकूलित होता है, इस तरह से कि छोटी संख्याओं द्वारा छोड़ी गई पाली नहीं होती है। सकारात्मक बाएं बदलाव हमेशा गणना के भाग के रूप में परिणाम को संग्रहीत करने के लिए एक बड़ा पूर्णांक ऑब्जेक्ट बनाते हैं, जबकि आपके परीक्षण में आपके द्वारा उपयोग किए गए सॉर्ट के गुणन के लिए, एक विशेष अनुकूलन इससे बचता है और सही आकार का पूर्णांक ऑब्जेक्ट बनाता है। इसे पायथन के पूर्णांक कार्यान्वयन के स्रोत कोड में देखा जा सकता है ।

क्योंकि पायथन में पूर्णांक मनमाने ढंग से सटीक होते हैं, उन्हें पूर्णांक अंक के बिट्स की संख्या की सीमा के साथ पूर्णांक "अंक" के सरणियों के रूप में संग्रहीत किया जाता है। तो सामान्य मामले में, पूर्णांकों से जुड़े संचालन एकल संचालन नहीं हैं, बल्कि इसके बजाय कई "अंकों" के मामले को संभालने की आवश्यकता है। में pyport.h , इस बिट सीमा के रूप में परिभाषित किया गया है अन्यथा 64-बिट प्लेटफॉर्म पर 30 बिट, या 15 बिट्स। (मैं स्पष्टीकरण को सरल रखने के लिए यहां से सिर्फ 30 पर कॉल करूंगा। लेकिन ध्यान दें कि यदि आप 32-बिट के लिए संकलित पायथन का उपयोग कर रहे थे, तो आपके बेंचमार्क का परिणाम इस बात पर निर्भर करेगा कि क्या x32,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()तो यह अपेक्षाकृत कम संख्या में संचालन पर ध्यान देगा, और स्टोर करने के लिए एकल-अंक पूर्णांक ऑब्जेक्ट बनाएँ। यह।

इसके विपरीत, बाईं पाली को इस तरह से अनुकूलित नहीं किया जाता है, और प्रत्येक बाईं पारी में पूर्णांक को एक सरणी के रूप में स्थानांतरित किया जाता है। विशेष रूप से, यदि आप 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()। यह शेष गुणा के साथ एक छोटे भाजक के लिए गणना की जाती है , और एक नव-आवंटित पूर्णांक ऑब्जेक्ट में संग्रहीत किया जाता है , जो इस स्थिति में तुरंत खारिज कर दिया जाता है।


1
यह विभाजन के साथ एक दिलचस्प अवलोकन है, इसे इंगित करने के लिए धन्यवाद। यह बिना कहे चला जाता है कि यह एक उत्कृष्ट उत्तर है।
hilberts_drinking_problem

एक अच्छी तरह से शोधित अदन एक उत्कृष्ट प्रश्न का लिखित उत्तर है। xअनुकूलित सीमा के बाहर के समय के लिए रेखांकन दिखाना दिलचस्प हो सकता है ।
बारमर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.