कुछ फ्लोट <पूर्णांक तुलना दूसरों की तुलना में चार गुना धीमी क्यों हैं?


284

जब फ्लोटर्स की तुलना पूर्णांकों से की जाती है, तो कुछ जोड़ियों का मूल्यांकन एक समान परिमाण के अन्य मूल्यों की तुलना में अधिक लंबा होता है।

उदाहरण के लिए:

>>> import timeit
>>> timeit.timeit("562949953420000.7 < 562949953421000") # run 1 million times
0.5387085462592742

लेकिन अगर फ्लोट या पूर्णांक एक निश्चित राशि से छोटा या बड़ा किया जाता है, तो तुलना बहुत अधिक तेजी से चलती है:

>>> timeit.timeit("562949953420000.7 < 562949953422000") # integer increased by 1000
0.1481498428446173
>>> timeit.timeit("562949953423001.8 < 562949953421000") # float increased by 3001.1
0.1459577925548956

तुलना ऑपरेटर को बदलना (उदाहरण के लिए ==या >इसके बजाय) किसी भी ध्यान देने योग्य तरीके से समय को प्रभावित नहीं करता है।

यह पूरी तरह से परिमाण से संबंधित नहीं है क्योंकि बड़े या छोटे मूल्यों को चुनने से परिणाम तेजी से हो सकते हैं, इसलिए मुझे संदेह है कि यह कुछ दुर्भाग्यपूर्ण तरीके से नीचे की ओर बिट्स लाइन अप है।

स्पष्ट रूप से, इन मूल्यों की तुलना सबसे अधिक उपयोग के मामलों के लिए तेजी से पर्याप्त है। मैं बस इस बात से उत्सुक हूं कि क्यों पायथन दूसरों के साथ मूल्यों के कुछ जोड़े के साथ अधिक संघर्ष करता है।


क्या यह 2.7 और 3.x दोनों में समान है?
Thefourtheye

उपर्युक्त समय Python 3.4 से हैं - 2.7 पर चलने वाले मेरे लिनक्स कंप्यूटर पर टाइमिंग (3 और 4-और-थोड़ा-थोड़ा समय के बीच) में एक समान विसंगति थी।
एलेक्स रिले

1
दिलचस्प लिखने के लिए धन्यवाद। मैं उत्सुक हूं कि इस सवाल से क्या प्रेरित हुआ - क्या आप केवल बेतरतीब ढंग से समय की तुलना कर रहे थे या इसके पीछे कोई कहानी है?
विड्रैक

3
@Veedrac: धन्यवाद। वहाँ एक कहानी का बहुत कुछ नहीं है: मैं अनुपस्थित-मन से सोचता था कि कैसे जल्दी से तैरता है और पूर्णांक की तुलना की जाती है, कुछ मूल्यों को देखा और कुछ छोटे अंतरों को देखा। तब मुझे एहसास हुआ कि मुझे बिल्कुल पता नहीं था कि पायथन फ्लोट्स और बड़े पूर्णांकों की सही तुलना कैसे कर पाया। मैंने स्रोत को समझने की कोशिश में कुछ समय बिताया और सीखा कि सबसे खराब स्थिति क्या है।
एलेक्स रिले

2
@YvesDaoust: उन विशिष्ट मूल्यों को नहीं, नहीं (जो कि अविश्वसनीय भाग्य होगा!)। मैंने मूल्यों के विभिन्न युग्मों की कोशिश की और समय में छोटे अंतरों पर ध्यान दिया (जैसे कि समान पूर्णांक बनाम बहुत बड़े पूर्णांक के साथ एक छोटे परिमाण के एक फ्लोट की तुलना करना)। मैंने 2 ^ 49 मामले के बारे में केवल यह जानने के लिए कि स्रोत ने यह देखने के लिए कि तुलना कैसे काम की है। मैंने सवाल में मूल्यों को चुना क्योंकि उन्होंने सबसे सम्मोहक तरीके से विषय को प्रस्तुत किया।
एलेक्स रिले

जवाबों:


354

फ्लोट ऑब्जेक्ट्स के लिए पायथन स्रोत कोड में एक टिप्पणी स्वीकार करती है कि:

तुलना बहुत बुरा सपना है

फ्लोट की तुलना पूर्णांक से करते समय यह विशेष रूप से सच है, क्योंकि, फ्लोट के विपरीत, पायथन में पूर्णांक मनमाने ढंग से बड़े हो सकते हैं और हमेशा सटीक होते हैं। पूर्णांक को फ्लोट में डालने की कोशिश करने से सटीकता खो सकती है और तुलना गलत हो सकती है। फ्लोट को एक पूर्णांक में डालने की कोशिश करना या तो काम करने वाला नहीं है क्योंकि कोई भी आंशिक हिस्सा खो जाएगा।

इस समस्या को हल करने के लिए, पायथन चेक की एक श्रृंखला करता है, यदि चेक में से कोई एक सफल होता है, तो परिणाम लौटाता है। यह दो मानों के संकेतों की तुलना करता है, फिर पूर्णांक "बहुत बड़ा" है, जो फ्लोट होने के लिए है, फिर फ्लोट के घातांक की तुलना पूर्णांक से करता है। यदि ये सभी चेक विफल हो जाते हैं, तो परिणाम प्राप्त करने के लिए तुलना करने के लिए दो नए पायथन ऑब्जेक्ट्स का निर्माण करना आवश्यक है।

जब एक फ्लोट की तुलना किसी vपूर्णांक / लंबे समय तक की जाती है w, तो सबसे खराब स्थिति यह होती है:

  • vऔर wएक ही संकेत है (सकारात्मक या नकारात्मक दोनों),
  • पूर्णांक wमें कुछ पर्याप्त बिट्स होते हैं जिन्हें यह size_tप्रकार में रखा जा सकता है (आमतौर पर 32 या 64 बिट्स),
  • पूर्णांक wमें कम से कम 49 बिट्स हैं,
  • फ्लोट vका प्रतिपादक बिट्स की संख्या के समान है w

और यह वही है जो हमारे पास सवाल में मूल्यों के लिए है:

>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49

हम देखते हैं कि पूर्णांक में फ्लोट का एक्सपोनेंट और बिट्स की संख्या दोनों हैं। दोनों संख्याएं सकारात्मक हैं और इसलिए उपरोक्त चार मानदंड पूरे किए गए हैं।

मूल्यों में से एक को बड़ा (या छोटा) चुनना पूर्णांक के बिट्स की संख्या, या घातांक के मूल्य को बदल सकता है, और इसलिए पायथन महंगी अंतिम जांच किए बिना तुलना के परिणाम को निर्धारित करने में सक्षम है।

यह भाषा के CPython कार्यान्वयन के लिए विशिष्ट है।


अधिक विस्तार से तुलना

float_richcompareसमारोह दो मानों के बीच तुलना को संभालती है vऔर w

नीचे फ़ंक्शन द्वारा निष्पादित चेक का चरण-दर-चरण वर्णन है। पायथन स्रोत में टिप्पणियां वास्तव में बहुत उपयोगी होती हैं, जब यह समझने की कोशिश की जाती है कि फ़ंक्शन क्या करता है, इसलिए मैंने उन्हें प्रासंगिक में छोड़ दिया है। मैंने उत्तर के चरणों में एक सूची में इन चेकों को संक्षेप में प्रस्तुत किया है।

मुख्य विचार पायथन ऑब्जेक्ट्स को मैप करना vऔर wदो उचित सी डबल्स के लिए है, iऔर j, जिसके बाद सही परिणाम देने के लिए आसानी से तुलना की जा सकती है। पाइथन 2 और पाइथन 3 दोनों ही ऐसा करने के लिए समान विचारों का उपयोग करते हैं (पूर्व सिर्फ हैंडल intऔर longप्रकार अलग से)।

पहली बात यह है कि vनिश्चित रूप से पायथन फ्लोट है और इसे सी डबल में मैप करें i। अगला फ़ंक्शन यह देखता है कि क्या wयह भी फ्लोट है और इसे सी डबल में मैप करता है j। यह फ़ंक्शन के लिए सबसे अच्छा मामला है क्योंकि अन्य सभी चेक को छोड़ दिया जा सकता है। फ़ंक्शन यह देखने के लिए भी जांच करता है कि क्या vहै infया nan:

static PyObject*
float_richcompare(PyObject *v, PyObject *w, int op)
{
    double i, j;
    int r = 0;
    assert(PyFloat_Check(v));       
    i = PyFloat_AS_DOUBLE(v);       

    if (PyFloat_Check(w))           
        j = PyFloat_AS_DOUBLE(w);   

    else if (!Py_IS_FINITE(i)) {
        if (PyLong_Check(w))
            j = 0.0;
        else
            goto Unimplemented;
    }

अब हम जानते हैं कि यदि wये जाँच विफल हो गई, तो यह पायथन फ्लोट नहीं है। अब फ़ंक्शन जाँचता है कि क्या यह पायथन पूर्णांक है। यदि यह मामला है, तो सबसे आसान परीक्षा है साइन इन करना vऔर साइन इन करना w(वापसी 0शून्य, -1यदि ऋणात्मक, 1यदि सकारात्मक)। यदि संकेत भिन्न हैं, तो तुलना के परिणाम को वापस करने के लिए आवश्यक सभी जानकारी है:

    else if (PyLong_Check(w)) {
        int vsign = i == 0.0 ? 0 : i < 0.0 ? -1 : 1;
        int wsign = _PyLong_Sign(w);
        size_t nbits;
        int exponent;

        if (vsign != wsign) {
            /* Magnitudes are irrelevant -- the signs alone
             * determine the outcome.
             */
            i = (double)vsign;
            j = (double)wsign;
            goto Compare;
        }
    }   

यदि यह जांच विफल हो गई, तो vऔर wउसी पर हस्ताक्षर करें।

अगला चेक पूर्णांक में बिट्स की संख्या को गिनता है w। यदि इसमें बहुत अधिक बिट्स हैं तो इसे संभवतः फ्लोट के रूप में नहीं रखा जा सकता है और यह फ्लोट की तुलना में परिमाण में बड़ा होना चाहिए v:

    nbits = _PyLong_NumBits(w);
    if (nbits == (size_t)-1 && PyErr_Occurred()) {
        /* This long is so large that size_t isn't big enough
         * to hold the # of bits.  Replace with little doubles
         * that give the same outcome -- w is so large that
         * its magnitude must exceed the magnitude of any
         * finite float.
         */
        PyErr_Clear();
        i = (double)vsign;
        assert(wsign != 0);
        j = wsign * 2.0;
        goto Compare;
    }

दूसरी ओर, यदि पूर्णांक wमें 48 या उससे कम बिट्स हैं, तो यह सुरक्षित रूप से C डबल jऔर तुलना में बदल सकता है :

    if (nbits <= 48) {
        j = PyLong_AsDouble(w);
        /* It's impossible that <= 48 bits overflowed. */
        assert(j != -1.0 || ! PyErr_Occurred());
        goto Compare;
    }

इस बिंदु से, हम जानते हैं कि w49 या अधिक बिट्स हैं। wसकारात्मक पूर्णांक के रूप में व्यवहार करना सुविधाजनक होगा , इसलिए साइन और तुलना ऑपरेटर को आवश्यक रूप से बदलें:

    if (nbits <= 48) {
        /* "Multiply both sides" by -1; this also swaps the
         * comparator.
         */
        i = -i;
        op = _Py_SwappedOp[op];
    }

अब फ़ंक्शन फ्लोट के घातांक को देखता है। स्मरण करो कि एक फ्लोट लिखा जा सकता है (संकेत को अनदेखा करते हुए) महत्व के रूप में * 2 घातांक और यह कि महत्व 0.5 / 1 के बीच एक संख्या का प्रतिनिधित्व करता है:

    (void) frexp(i, &exponent);
    if (exponent < 0 || (size_t)exponent < nbits) {
        i = 1.0;
        j = 2.0;
        goto Compare;
    }

यह दो चीजों की जाँच करता है। यदि प्रतिपादक 0 से कम है तो फ्लोट 1 से छोटा है (और किसी भी पूर्णांक की तुलना में परिमाण में छोटा है)। या, यदि प्रतिपादक बिट्स की संख्या से कम है, wतो हमारे पास यह है कि v < |w|महत्व * 2 घातांक 2 nbit से कम है ।

इन दो जांचों को विफल करते हुए, फ़ंक्शन यह देखता है कि एक्सपोनेंट बिट की संख्या से अधिक है या नहीं w। यह दिखाता है कि significand * 2 प्रतिपादक 2 से अधिक है nbits और इतने v > |w|:

    if ((size_t)exponent > nbits) {
        i = 2.0;
        j = 1.0;
        goto Compare;
    }

यदि यह जांच सफल नहीं हुई, तो हम जानते हैं कि फ्लोट vका घातांक पूर्णांक में बिट्स की संख्या के समान है w

एकमात्र तरीका यह है कि अब दो मूल्यों की तुलना की जा सकती है vऔर इसमें से दो नए पायथन पूर्णांक का निर्माण करना है w। विचार यह है कि आंशिक भाग को छोड़ दें v, पूर्णांक भाग को दोगुना करें, और फिर एक जोड़ें। wको दोगुना भी किया जाता है और इन दो नए पायथन ऑब्जेक्ट्स की तुलना सही रिटर्न वैल्यू देने के लिए की जा सकती है। छोटे मूल्यों के साथ एक उदाहरण का उपयोग करना, 4.65 < 4तुलना (2*4)+1 == 9 < 8 == (2*4)(झूठे वापस करने) द्वारा निर्धारित किया जाएगा ।

    {
        double fracpart;
        double intpart;
        PyObject *result = NULL;
        PyObject *one = NULL;
        PyObject *vv = NULL;
        PyObject *ww = w;

        // snip

        fracpart = modf(i, &intpart); // split i (the double that v mapped to)
        vv = PyLong_FromDouble(intpart);

        // snip

        if (fracpart != 0.0) {
            /* Shift left, and or a 1 bit into vv
             * to represent the lost fraction.
             */
            PyObject *temp;

            one = PyLong_FromLong(1);

            temp = PyNumber_Lshift(ww, one); // left-shift doubles an integer
            ww = temp;

            temp = PyNumber_Lshift(vv, one);
            vv = temp;

            temp = PyNumber_Or(vv, one); // a doubled integer is even, so this adds 1
            vv = temp;
        }
        // snip
    }
}

संक्षिप्तता के लिए मैंने अतिरिक्त त्रुटि-जांच और कचरा-ट्रैकिंग पायथन को छोड़ दिया है, जब यह इन नई वस्तुओं को बनाता है। कहने की जरूरत नहीं है, यह अतिरिक्त उपरि जोड़ता है और बताता है कि क्यों सवाल में हाइलाइट किए गए मान दूसरों की तुलना में काफी धीमा हैं।


यहां उन चेकों का सारांश दिया गया है जो तुलनात्मक कार्य द्वारा किए जाते हैं।

चलो vएक फ्लोट बनें और इसे सी डबल के रूप में कास्ट करें। अब, अगर wयह भी एक नाव है:

  • जाँच करें कि क्या wहै nanया inf। यदि हां, तो इस विशेष मामले को अलग प्रकार के आधार पर संभालें w

  • यदि नहीं, तो सी डबल्स के रूप में उनके अभ्यावेदन से तुलना करें vऔर wसीधे।

यदि wएक पूर्णांक है:

  • के संकेत निकालें vऔर w। यदि वे भिन्न हैं तो हम जानते हैं vऔर wभिन्न हैं और जो अधिक मूल्य है।

  • ( संकेत समान हैं। ) जांचें कि क्या wकई बिट्स एक फ्लोट (अधिक से अधिक size_t) होने के लिए हैं। यदि ऐसा है, wकी तुलना में अधिक परिमाण है v

  • जाँच करें कि wक्या 48 या उससे कम बिट्स हैं। यदि हां, तो इसे अपनी सटीकता खोए बिना और तुलना के साथ सी डबल में सुरक्षित रूप से डाला जा सकता है v

  • ( w48 से अधिक बिट्स हैं। अब हम wएक सकारात्मक पूर्णांक के रूप में व्यवहार करेंगे, जिसमें तुलनात्मक ऑप्स को उपयुक्त रूप से बदल दिया जाएगा। )

  • फ्लोट के घातांक पर विचार करें v। यदि घातांक नकारात्मक है, तो किसी भी सकारात्मक पूर्णांक से vकम है 1और इसलिए कम है। और, यदि प्रतिपादक बिट्स की संख्या से कम है wतो यह उससे कम होना चाहिए w

  • यदि का प्रतिपादक vबिट्स की संख्या से अधिक है wतो vकी तुलना में अधिक है w

  • ( प्रतिपादक बिट की संख्या के समान है w )

  • अंतिम जाँच। vइसके पूर्णांक और भिन्नात्मक भागों में विभाजित करें । पूर्णांक भाग को दोगुना करें और आंशिक भाग की भरपाई के लिए 1 जोड़ें। अब पूर्णांक को दोगुना करें w। परिणाम प्राप्त करने के बजाय इन दो नए पूर्णांकों की तुलना करें।


4
अच्छी तरह से किया गया पायथन डेवलपर्स - अधिकांश भाषा कार्यान्वयनों ने फ्लोट / पूर्णांक तुलनाओं को सटीक नहीं कहकर समस्या को हल किया होगा।
11:25 बजे user253751

4

gmpy2मनमाने ढंग से सटीक झांकियों और पूर्णांक के साथ उपयोग करने पर अधिक समान तुलना प्रदर्शन प्राप्त करना संभव है:

~ $ ptipython
Python 3.5.1 |Anaconda 4.0.0 (64-bit)| (default, Dec  7 2015, 11:16:01) 
Type "copyright", "credits" or "license" for more information.

IPython 4.1.2 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import gmpy2

In [2]: from gmpy2 import mpfr

In [3]: from gmpy2 import mpz

In [4]: gmpy2.get_context().precision=200

In [5]: i1=562949953421000

In [6]: i2=562949953422000

In [7]: f=562949953420000.7

In [8]: i11=mpz('562949953421000')

In [9]: i12=mpz('562949953422000')

In [10]: f1=mpfr('562949953420000.7')

In [11]: f<i1
Out[11]: True

In [12]: f<i2
Out[12]: True

In [13]: f1<i11
Out[13]: True

In [14]: f1<i12
Out[14]: True

In [15]: %timeit f<i1
The slowest run took 10.15 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 441 ns per loop

In [16]: %timeit f<i2
The slowest run took 12.55 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 152 ns per loop

In [17]: %timeit f1<i11
The slowest run took 32.04 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 269 ns per loop

In [18]: %timeit f1<i12
The slowest run took 36.81 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 231 ns per loop

In [19]: %timeit f<i11
The slowest run took 78.26 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 156 ns per loop

In [20]: %timeit f<i12
The slowest run took 21.24 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 194 ns per loop

In [21]: %timeit f1<i1
The slowest run took 37.61 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 275 ns per loop

In [22]: %timeit f1<i2
The slowest run took 39.03 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 259 ns per loop

1
मैंने अभी तक इस लाइब्रेरी का उपयोग नहीं किया है, लेकिन यह संभावित रूप से बहुत उपयोगी है। धन्यवाद!
एलेक्स रिले

इसका उपयोग सिम्पी और एमपाथ द्वारा किया जाता है
डेन्फ्रोमुफा

CPython decimal
denfromufa
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.