= X ’in ('x’), = x ’==? X’ से अधिक क्यों है?


274
>>> timeit.timeit("'x' in ('x',)")
0.04869917374131205
>>> timeit.timeit("'x' == 'x'")
0.06144205736110564

कई तत्वों के साथ टुपल्स के लिए भी काम करता है, दोनों संस्करण रैखिक रूप से बढ़ते हैं:

>>> timeit.timeit("'x' in ('x', 'y')")
0.04866674801541748
>>> timeit.timeit("'x' == 'x' or 'x' == 'y'")
0.06565782838087131
>>> timeit.timeit("'x' in ('y', 'x')")
0.08975995576448526
>>> timeit.timeit("'x' == 'y' or 'x' == 'y'")
0.12992391047427532

इसके आधार पर, मुझे लगता है कि मुझे पूरी तरह सेin इसके बजाय हर जगह का उपयोग शुरू करना चाहिए ==!


167
बस के मामले में: के inबजाय हर जगह का उपयोग शुरू मत करो ==। यह एक समयपूर्व अनुकूलन है जो पठनीयता को हानि पहुँचाता है।
कर्नल थर्टी दो

4
कोशिश करो x ="!foo" x in ("!foo",)औरx == "!foo"
Padraic कनिंघम

2
A में B = मान, C == D मान और प्रकार तुलना
dsgdfg

6
सी के लिए उपयोग inकरने की तुलना में एक अधिक उचित दृष्टिकोण है ==सी
मैड फिजिसिस्ट

1
यदि आप पायथन में लिख रहे हैं और आप गति के लिए दूसरे पर एक निर्माण चुनते हैं, तो आप इसे गलत कर रहे हैं।
वेकी

जवाबों:


257

जैसा कि मैंने डेविड वोलेवर का उल्लेख किया है, आँख से मिलने की तुलना में यह अधिक है; दोनों तरीकों से प्रेषण is; आप ऐसा करके साबित कर सकते हैं

min(Timer("x == x", setup="x = 'a' * 1000000").repeat(10, 10000))
#>>> 0.00045456900261342525

min(Timer("x == y", setup="x = 'a' * 1000000; y = 'a' * 1000000").repeat(10, 10000))
#>>> 0.5256857610074803

पहला केवल इतना तेज़ हो सकता है क्योंकि यह पहचान से जाँच करता है।

यह पता लगाने के लिए कि कोई दूसरे से अधिक समय क्यों लेगा, आइए निष्पादन के माध्यम से ट्रेस करें।

वे दोनों अंदर से शुरू करते हैं ceval.c, COMPARE_OPजिसमें से बाईटकोड शामिल है

TARGET(COMPARE_OP) {
    PyObject *right = POP();
    PyObject *left = TOP();
    PyObject *res = cmp_outcome(oparg, left, right);
    Py_DECREF(left);
    Py_DECREF(right);
    SET_TOP(res);
    if (res == NULL)
        goto error;
    PREDICT(POP_JUMP_IF_FALSE);
    PREDICT(POP_JUMP_IF_TRUE);
    DISPATCH();
}

यह स्टैक से मानों को पॉप करता है (तकनीकी रूप से यह केवल एक पॉप करता है)

PyObject *right = POP();
PyObject *left = TOP();

और तुलना चलाता है:

PyObject *res = cmp_outcome(oparg, left, right);

cmp_outcome क्या यह:

static PyObject *
cmp_outcome(int op, PyObject *v, PyObject *w)
{
    int res = 0;
    switch (op) {
    case PyCmp_IS: ...
    case PyCmp_IS_NOT: ...
    case PyCmp_IN:
        res = PySequence_Contains(w, v);
        if (res < 0)
            return NULL;
        break;
    case PyCmp_NOT_IN: ...
    case PyCmp_EXC_MATCH: ...
    default:
        return PyObject_RichCompare(v, w, op);
    }
    v = res ? Py_True : Py_False;
    Py_INCREF(v);
    return v;
}

यहीं से रास्ते बिखर जाते हैं। PyCmp_INशाखा करता है

int
PySequence_Contains(PyObject *seq, PyObject *ob)
{
    Py_ssize_t result;
    PySequenceMethods *sqm = seq->ob_type->tp_as_sequence;
    if (sqm != NULL && sqm->sq_contains != NULL)
        return (*sqm->sq_contains)(seq, ob);
    result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS);
    return Py_SAFE_DOWNCAST(result, Py_ssize_t, int);
}

ध्यान दें कि एक टपल को परिभाषित किया गया है

static PySequenceMethods tuple_as_sequence = {
    ...
    (objobjproc)tuplecontains,                  /* sq_contains */
};

PyTypeObject PyTuple_Type = {
    ...
    &tuple_as_sequence,                         /* tp_as_sequence */
    ...
};

तो शाखा

if (sqm != NULL && sqm->sq_contains != NULL)

लिया जाएगा और *sqm->sq_contains, जो कार्य है (objobjproc)tuplecontains, लिया जाएगा।

यह करता है

static int
tuplecontains(PyTupleObject *a, PyObject *el)
{
    Py_ssize_t i;
    int cmp;

    for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
        cmp = PyObject_RichCompareBool(el, PyTuple_GET_ITEM(a, i),
                                           Py_EQ);
    return cmp;
}

... रुको, यह नहीं था कि PyObject_RichCompareBoolदूसरी शाखा ने क्या लिया? नहीं, कि था PyObject_RichCompare

यह कोड पथ छोटा था इसलिए यह संभवत: बस इन दोनों की गति के नीचे आ गया। आइए तुलना करते हैं।

int
PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
{
    PyObject *res;
    int ok;

    /* Quick result when objects are the same.
       Guarantees that identity implies equality. */
    if (v == w) {
        if (op == Py_EQ)
            return 1;
        else if (op == Py_NE)
            return 0;
    }

    ...
}

PyObject_RichCompareBoolबहुत ज्यादा तुरंत कोड कोड समाप्त हो जाता है। के लिए PyObject_RichCompare, यह करता है

PyObject *
PyObject_RichCompare(PyObject *v, PyObject *w, int op)
{
    PyObject *res;

    assert(Py_LT <= op && op <= Py_GE);
    if (v == NULL || w == NULL) { ... }
    if (Py_EnterRecursiveCall(" in comparison"))
        return NULL;
    res = do_richcompare(v, w, op);
    Py_LeaveRecursiveCall();
    return res;
}

Py_EnterRecursiveCall/ Py_LeaveRecursiveCallकॉम्बो पिछले रास्ते में नहीं रखा जाता है, लेकिन इन अपेक्षाकृत जल्दी मैक्रो हैं कि हूँ incrementing और कुछ वैश्विक decrementing के बाद शॉर्ट सर्किट।

do_richcompare कर देता है:

static PyObject *
do_richcompare(PyObject *v, PyObject *w, int op)
{
    richcmpfunc f;
    PyObject *res;
    int checked_reverse_op = 0;

    if (v->ob_type != w->ob_type && ...) { ... }
    if ((f = v->ob_type->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        ...
    }
    ...
}

यह कॉल करने के लिए कुछ त्वरित जांच करता v->ob_type->tp_richcompareहै

PyTypeObject PyUnicode_Type = {
    ...
    PyUnicode_RichCompare,      /* tp_richcompare */
    ...
};

जो करता है

PyObject *
PyUnicode_RichCompare(PyObject *left, PyObject *right, int op)
{
    int result;
    PyObject *v;

    if (!PyUnicode_Check(left) || !PyUnicode_Check(right))
        Py_RETURN_NOTIMPLEMENTED;

    if (PyUnicode_READY(left) == -1 ||
        PyUnicode_READY(right) == -1)
        return NULL;

    if (left == right) {
        switch (op) {
        case Py_EQ:
        case Py_LE:
        case Py_GE:
            /* a string is equal to itself */
            v = Py_True;
            break;
        case Py_NE:
        case Py_LT:
        case Py_GT:
            v = Py_False;
            break;
        default:
            ...
        }
    }
    else if (...) { ... }
    else { ...}
    Py_INCREF(v);
    return v;
}

अर्थात्, यह शॉर्टकट left == right... पर केवल करने के बाद

    if (!PyUnicode_Check(left) || !PyUnicode_Check(right))

    if (PyUnicode_READY(left) == -1 ||
        PyUnicode_READY(right) == -1)

सभी रास्तों में सभी कुछ इस तरह से देखते हैं (मैन्युअल रूप से पुनरावर्ती inlining, unrolling और pruning ज्ञात शाखाएं)

POP()                           # Stack stuff
TOP()                           #
                                #
case PyCmp_IN:                  # Dispatch on operation
                                #
sqm != NULL                     # Dispatch to builtin op
sqm->sq_contains != NULL        #
*sqm->sq_contains               #
                                #
cmp == 0                        # Do comparison in loop
i < Py_SIZE(a)                  #
v == w                          #
op == Py_EQ                     #
++i                             # 
cmp == 0                        #
                                #
res < 0                         # Convert to Python-space
res ? Py_True : Py_False        #
Py_INCREF(v)                    #
                                #
Py_DECREF(left)                 # Stack stuff
Py_DECREF(right)                #
SET_TOP(res)                    #
res == NULL                     #
DISPATCH()                      #

बनाम

POP()                           # Stack stuff
TOP()                           #
                                #
default:                        # Dispatch on operation
                                #
Py_LT <= op                     # Checking operation
op <= Py_GE                     #
v == NULL                       #
w == NULL                       #
Py_EnterRecursiveCall(...)      # Recursive check
                                #
v->ob_type != w->ob_type        # More operation checks
f = v->ob_type->tp_richcompare  # Dispatch to builtin op
f != NULL                       #
                                #
!PyUnicode_Check(left)          # ...More checks
!PyUnicode_Check(right))        #
PyUnicode_READY(left) == -1     #
PyUnicode_READY(right) == -1    #
left == right                   # Finally, doing comparison
case Py_EQ:                     # Immediately short circuit
Py_INCREF(v);                   #
                                #
res != Py_NotImplemented        #
                                #
Py_LeaveRecursiveCall()         # Recursive check
                                #
Py_DECREF(left)                 # Stack stuff
Py_DECREF(right)                #
SET_TOP(res)                    #
res == NULL                     #
DISPATCH()                      #

अब, PyUnicode_Checkऔर PyUnicode_READYबहुत सस्ते हैं क्योंकि वे केवल कुछ क्षेत्रों की जांच करते हैं, लेकिन यह स्पष्ट होना चाहिए कि शीर्ष एक छोटा कोड पथ है, इसमें कम फ़ंक्शन कॉल हैं, केवल एक स्विच स्टेटमेंट है और बस थोड़ा पतला है।

टी एल; डॉ:

दोनों को प्रेषण if (left_pointer == right_pointer); फर्क सिर्फ इतना है कि वे वहां पहुंचने के लिए कितना काम करते हैं। inबस कम करता है।


18
यह एक अविश्वसनीय जवाब है। अजगर परियोजना से आपका क्या संबंध है?
kdbanman

9
@kdbanman कोई नहीं, वास्तव में, हालांकि मैं अपने तरीके से थोड़ा बल देने में कामयाब रहा हूं ;)।
विड्रैक

21
@varepsilon Aww, लेकिन फिर कोई भी वास्तविक पोस्ट को परेशान नहीं करेगा! प्रश्न का बिंदु वास्तव में उत्तर नहीं है, लेकिन इस प्रक्रिया का उत्तर प्राप्त करने के लिए उपयोग किया जाता है - उम्मीद है कि उत्पादन में इस हैक का उपयोग करने वाले लोगों का एक टन नहीं होगा!
वैड्रेक

181

यहाँ खेलने के तीन कारक हैं, जो संयुक्त रूप से इस आश्चर्यजनक व्यवहार का उत्पादन करते हैं।

पहले: inऑपरेटर एक शॉर्टकट लेता है और पहचान की जांच करता है ( x is y) इससे पहले कि वह समानता की जांच करे ( x == y):

>>> n = float('nan')
>>> n in (n, )
True
>>> n == n
False
>>> n is n
True

दूसरा: क्योंकि पायथन के तार का होना शामिल है, दोनों "x"में "x" in ("x", )एक समान होगा:

>>> "x" is "x"
True

(बड़ा चेतावनी: इस कार्यान्वयन-विशिष्ट व्यवहार है! isचाहिए कभी नहीं तार तुलना करने के लिए है क्योंकि यह इस्तेमाल किया जा जाएगा आश्चर्य की बात के जवाब कभी कभी दे, उदाहरण के लिए "x" * 100 is "x" * 100 ==> False)

तीसरा: के रूप में में विस्तृत Veedrac के शानदार जवाब , tuple.__contains__( x in (y, )है मोटे तौर पर बराबर करने के लिए (y, ).__contains__(x)तेजी से पहचान जांच के प्रदर्शन की बात करने के हो जाता है) str.__eq__(फिर से, x == yहै मोटे तौर पर के बराबर x.__eq__(y)होता है)।

आप इसके लिए सबूत देख सकते हैं क्योंकि x in (y, )तार्किक रूप से समकक्ष की तुलना में काफी धीमी है x == y:

In [18]: %timeit 'x' in ('x', )
10000000 loops, best of 3: 65.2 ns per loop

In [19]: %timeit 'x' == 'x'    
10000000 loops, best of 3: 68 ns per loop

In [20]: %timeit 'x' in ('y', ) 
10000000 loops, best of 3: 73.4 ns per loop

In [21]: %timeit 'x' == 'y'    
10000000 loops, best of 3: 56.2 ns per loop

x in (y, )मामले में धीमी है, क्योंकि के बाद है isतुलना में विफल रहता है, inऑपरेटर वापस सामान्य समानता की जाँच करने के लिए गिर जाता है (यानी, का उपयोग करते हुए ==,) तो तुलना के रूप में उतना ही समय के बारे में लेता है ==, टपल बनाने की भूमि के ऊपर की वजह से धीमी पूरे आपरेशन प्रतिपादन , इसके सदस्यों को चलना, आदि।

यह भी ध्यान रखें कि a in (b, )है केवल तेजी से जब a is b:

In [48]: a = 1             

In [49]: b = 2

In [50]: %timeit a is a or a == a
10000000 loops, best of 3: 95.1 ns per loop

In [51]: %timeit a in (a, )      
10000000 loops, best of 3: 140 ns per loop

In [52]: %timeit a is b or a == b
10000000 loops, best of 3: 177 ns per loop

In [53]: %timeit a in (b, )      
10000000 loops, best of 3: 169 ns per loop

(इससे a in (b, )अधिक तेज़ क्यों है a is b or a == b? मेरा अनुमान कम वर्चुअल मशीन निर्देश होगा -  a in (b, )केवल ~ 3 निर्देश हैं, जहां a is b or a == bबहुत अधिक वीएम निर्देश होंगे)

Veedrac का जवाब - https://stackoverflow.com/a/28889838/71522 - विशेष रूप से प्रत्येक के दौरान क्या होता है ==और inपढ़ने के लिए अच्छी तरह से होता है पर अधिक विस्तार से जाता है ।


3
और कारण यह इस संभावना की अनुमति देना है करता है X in [X,Y,Z]काम करने के लिए बिना सही ढंग से X, Yया Zसमानता पद्धतियां निर्धारित करने के लिए होने (या डिफ़ॉल्ट रूप समानता है is, तो यह कॉल करने के लिए होने की बचत होती है __eq__कोई उपयोगकर्ता परिभाषित के साथ वस्तुओं पर __eq__और isसही किया जा रहा मतलब चाहिए मूल्य -equality)।
अर्जुनदन्त

1
का उपयोग float('nan')संभावित भ्रामक है। यह इस बात का गुण है nanकि यह स्वयं के बराबर नहीं है। वह समय बदल सकता है
dawg

@dawg आह, अच्छी बात - नैन उदाहरण सिर्फ inसदस्यता परीक्षणों पर शॉर्टकट लेने के लिए वर्णन करने के लिए था । मैं स्पष्ट करने के लिए चर नाम बदल दूँगा।
डेविड वोलेवर

3
जहां तक ​​मैं समझता हूं, CPython में 3.4.3 tuple.__contains__लागू किया गया है, tuplecontainsजो PyObject_RichCompareBoolकि कॉल और पहचान के मामले में तुरंत लौटता है। unicodeहै PyUnicode_RichCompareहुड, जो पहचान के लिए एक ही शॉर्टकट है के तहत।
क्रिस्टियन सियुपिटु

3
इसका मतलब है कि "x" is "x"जरूरी नहीं है True'x' in ('x', )हमेशा रहेगा True, लेकिन हो सकता है कि यह तेजी से दिखाई न दे ==
डेविड वोलेवर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.