কেন '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"
প্যাডেরিক কানিংহাম

2
এ বি = মান, সি == ডি মান এবং প্রকারের তুলনা
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);
}

নোট করুন যে একটি tuple হিসাবে সংজ্ঞায়িত করা হয়

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কম্বো পূর্ববর্তী পথে নিয়ে যাওয়া হয় না, কিন্তু এই অপেক্ষাকৃত দ্রুত ম্যাক্রো আছে হবে বৃদ্ধিশীল এবং কিছু globals 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)

সমস্ত পাথের সমস্তটিতে এর পরে এমন কিছু চেহারা (ম্যানুয়ালি পুনরাবৃত্তভাবে ইনলাইনিং, তালিকাবদ্ধকরণ এবং পরিচিত শাখাগুলি ছাঁটাই করা)

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
@ কেডব্যানম্যান কিছুই নেই, সত্যই, যদিও আমি আমার পথে কিছুটা চাপ প্রয়োগ করতে পেরেছি ;)।
Veedrac

21
@ ওয়ারেপসিলন ওউ, তবে তারপরে কেউ আসল পোস্টকে স্কিমিং করতে বিরক্ত করবেন না! প্রশ্নের বিন্দুটি সত্যই উত্তর নয় তবে প্রক্রিয়াটি উত্তর পেতে ব্যবহৃত হয়েছিল - আশা করা যায় যে এই হ্যাকটি উত্পাদনে ব্যবহার করে এমন এক টন লোক থাকবে না!
Veedrac

181

এখানে খেলতে তিনটি কারণ রয়েছে যা সম্মিলিতভাবে এই আশ্চর্যজনক আচরণের জন্ম দেয়।

প্রথম: inঅপারেটর একটি শর্টকাট নেয় এবং x is yএটি সমতা ( x == y) পরীক্ষা করার আগে পরিচয় ( ) পরীক্ষা করে :

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

দ্বিতীয়ত: কারণ পাইথন এর স্ট্রিং এর interning, উভয় "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অপারেটর স্বাভাবিক সমতা পরীক্ষণ বৃক্ষের পতন (অর্থাত, ব্যবহার ==,) তাই তুলনা সময় একই পরিমাণ সময় লাগে ==, tuple তৈরি করার ওভারহেড কারণে ধীর সমগ্র অপারেশন রেন্ডারিং , এর সদস্যদের হাঁটা ইত্যাদি।

নোট যে 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সত্য হচ্ছে পরোক্ষভাবে উচিত মান -সাম্য)।
অরুইসদানেতে

1
এর ব্যবহার float('nan')সম্ভাব্য বিভ্রান্তিকর। এটি nanনিজের সমতুল্য নয় এমন একটি সম্পত্তি । যে পারে সময়জ্ঞান পরিবর্তন করুন।
ডগ

@ ডগ আহ, ভাল কথা - ন্যান উদাহরণটি কেবল শর্টকাটটি inসদস্যতার পরীক্ষাগুলির জন্য তুলে ধরা বোঝানো হয়েছিল । আমি পরিবর্তনশীল নামটি পরিষ্কার করে দেব rif
ডেভিড ওলেভার

3
যতদূর আমি বুঝতে পেরেছি, সিপিথনে ৩.৪.৩ tuple.__contains__প্রয়োগ করা হয়েছে tuplecontainsযার মাধ্যমে কলগুলি PyObject_RichCompareBoolএবং এটি পরিচয়ের ক্ষেত্রে অবিলম্বে ফিরে আসে। unicodeহয়েছে PyUnicode_RichCompareফণা, যা পরিচয় জন্য একই শর্টকাট রয়েছে অধীনে।
ক্রিশ্চিয়ান সিপিতু

3
এর অর্থ এটি "x" is "x"অগত্যা হবে না True'x' in ('x', )সর্বদা থাকবে Trueতবে এটির চেয়ে দ্রুত হতে পারে না ==
ডেভিড ওলবার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.