एक छोटी सूची की तुलना में एक छोटे स्ट्रिंग पर पुनरावृति करना क्यों धीमा है?


132

मैं समय के साथ खेल रहा था और देखा कि एक छोटी सी स्ट्रिंग पर एक साधारण सूची की समझ बनाने में छोटे एकल चरित्र के तारों की सूची पर एक ही ऑपरेशन करने में अधिक समय लगता था। कोई स्पष्टीकरण? यह लगभग 1.35 गुना ज्यादा समय है।

>>> from timeit import timeit
>>> timeit("[x for x in 'abc']")
2.0691067844831528
>>> timeit("[x for x in ['a', 'b', 'c']]")
1.5286479570345861

यह किस निचले स्तर पर हो रहा है?

जवाबों:


193

टी एल; डॉ

  • पाइथन 2 के लिए ओवरहेड को हटाने के बाद वास्तविक गति अंतर 70% (या अधिक) के करीब है।

  • ऑब्जेक्ट क्रिएशन गलती पर नहीं है। न तो विधि एक नई वस्तु बनाती है, क्योंकि एक-वर्ण तार को कैश किया जाता है।

  • अंतर स्पष्ट नहीं है, लेकिन संभवत: प्रकार और अच्छी तरह से गठन के संबंध में, स्ट्रिंग इंडेक्सिंग पर अधिक संख्या में चेक से बनाया गया है। यह भी जांचने की आवश्यकता के लिए धन्यवाद है कि क्या लौटना है।

  • सूची अनुक्रमण उल्लेखनीय रूप से तेज है।



>>> python3 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.388 usec per loop

>>> python3 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.436 usec per loop

आपने जो पाया है, उससे यह असहमत है ...

आप पायथन 2 का उपयोग कर रहे होंगे, तब।

>>> python2 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.309 usec per loop

>>> python2 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.212 usec per loop

आइए संस्करणों के बीच अंतर को स्पष्ट करते हैं। मैं संकलित कोड की जांच करूंगा।

पायथन 3 के लिए:

import dis

def list_iterate():
    [item for item in ["a", "b", "c"]]

dis.dis(list_iterate)
#>>>   4           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d06b118a0, file "", line 4>)
#>>>               3 LOAD_CONST               2 ('list_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               3 ('a')
#>>>              12 LOAD_CONST               4 ('b')
#>>>              15 LOAD_CONST               5 ('c')
#>>>              18 BUILD_LIST               3
#>>>              21 GET_ITER
#>>>              22 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              25 POP_TOP
#>>>              26 LOAD_CONST               0 (None)
#>>>              29 RETURN_VALUE

def string_iterate():
    [item for item in "abc"]

dis.dis(string_iterate)
#>>>  21           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d06b17150, file "", line 21>)
#>>>               3 LOAD_CONST               2 ('string_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               3 ('abc')
#>>>              12 GET_ITER
#>>>              13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              16 POP_TOP
#>>>              17 LOAD_CONST               0 (None)
#>>>              20 RETURN_VALUE

आप यहाँ देखते हैं कि हर बार सूची के निर्माण के कारण सूची प्रकार धीमा होने की संभावना है।

यह है

 9 LOAD_CONST   3 ('a')
12 LOAD_CONST   4 ('b')
15 LOAD_CONST   5 ('c')
18 BUILD_LIST   3

अंश। स्ट्रिंग संस्करण केवल है

 9 LOAD_CONST   3 ('abc')

आप देख सकते हैं कि इससे फर्क पड़ता है:

def string_iterate():
    [item for item in ("a", "b", "c")]

dis.dis(string_iterate)
#>>>  35           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d068be660, file "", line 35>)
#>>>               3 LOAD_CONST               2 ('string_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               6 (('a', 'b', 'c'))
#>>>              12 GET_ITER
#>>>              13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              16 POP_TOP
#>>>              17 LOAD_CONST               0 (None)
#>>>              20 RETURN_VALUE

यह सिर्फ उत्पादन करता है

 9 LOAD_CONST               6 (('a', 'b', 'c'))

के रूप में tuples अपरिवर्तनीय हैं। परीक्षा:

>>> python3 -m timeit '[x for x in ("a", "b", "c")]'
1000000 loops, best of 3: 0.369 usec per loop

महान, गति के लिए वापस।

पायथन 2 के लिए:

def list_iterate():
    [item for item in ["a", "b", "c"]]

dis.dis(list_iterate)
#>>>   2           0 BUILD_LIST               0
#>>>               3 LOAD_CONST               1 ('a')
#>>>               6 LOAD_CONST               2 ('b')
#>>>               9 LOAD_CONST               3 ('c')
#>>>              12 BUILD_LIST               3
#>>>              15 GET_ITER            
#>>>         >>   16 FOR_ITER                12 (to 31)
#>>>              19 STORE_FAST               0 (item)
#>>>              22 LOAD_FAST                0 (item)
#>>>              25 LIST_APPEND              2
#>>>              28 JUMP_ABSOLUTE           16
#>>>         >>   31 POP_TOP             
#>>>              32 LOAD_CONST               0 (None)
#>>>              35 RETURN_VALUE        

def string_iterate():
    [item for item in "abc"]

dis.dis(string_iterate)
#>>>   2           0 BUILD_LIST               0
#>>>               3 LOAD_CONST               1 ('abc')
#>>>               6 GET_ITER            
#>>>         >>    7 FOR_ITER                12 (to 22)
#>>>              10 STORE_FAST               0 (item)
#>>>              13 LOAD_FAST                0 (item)
#>>>              16 LIST_APPEND              2
#>>>              19 JUMP_ABSOLUTE            7
#>>>         >>   22 POP_TOP             
#>>>              23 LOAD_CONST               0 (None)
#>>>              26 RETURN_VALUE        

अजीब बात यह है कि हमारे पास सूची का एक ही भवन है, लेकिन इसके लिए यह अभी भी तेज है। अजगर 2 अजीब तरह से काम कर रहा है।

चलो समझ और फिर से समय निकालें। _ =इसे बाहर अनुकूलित हो रही रोकने के लिए है।

>>> python3 -m timeit '_ = ["a", "b", "c"]'
10000000 loops, best of 3: 0.0707 usec per loop

>>> python3 -m timeit '_ = "abc"'
100000000 loops, best of 3: 0.0171 usec per loop

हम देख सकते हैं कि संस्करणों के बीच अंतर के लिए प्रारंभिककरण महत्वपूर्ण नहीं है (वे संख्याएं छोटी हैं)! हम इस तरह से निष्कर्ष निकाल सकते हैं कि पायथन 3 में धीमी समझ है। यह समझ में आता है क्योंकि पाइथन 3 ने सुरक्षित स्कोपिंग की संभावना को बदल दिया है।

ठीक है, अब बेंचमार्क में सुधार करें (मैं सिर्फ ओवरहेड को हटा रहा हूं जो पुनरावृत्ति नहीं है)। इसे पूर्व-असाइन करके पुनरावृति के भवन को हटा दिया जाता है:

>>> python3 -m timeit -s 'iterable = "abc"'           '[x for x in iterable]'
1000000 loops, best of 3: 0.387 usec per loop

>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
1000000 loops, best of 3: 0.368 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"'           '[x for x in iterable]'
1000000 loops, best of 3: 0.309 usec per loop

>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
10000000 loops, best of 3: 0.164 usec per loop

हम जाँच सकते हैं कि क्या कॉल iterओवरहेड है:

>>> python3 -m timeit -s 'iterable = "abc"'           'iter(iterable)'
10000000 loops, best of 3: 0.099 usec per loop

>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.1 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"'           'iter(iterable)'
10000000 loops, best of 3: 0.0913 usec per loop

>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.0854 usec per loop

नहीं, यह नहीं है। अंतर बहुत छोटा है, खासकर पायथन 3 के लिए।

तो चलिए अभी और अनचाहे ओवरहेड को हटाते हैं ... पूरी चीज़ को धीमा करके! उद्देश्य बस एक लंबी यात्रा करना है, इसलिए समय अधिक हो जाता है।

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 3.12 msec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.77 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 2.32 msec per loop

>>> python2 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.09 msec per loop

यह वास्तव में ज्यादा नहीं बदला है , लेकिन यह थोड़ा मदद की है।

इसलिए समझ को निकालो। यह ओवरहेड है जो प्रश्न का हिस्सा नहीं है:

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.71 msec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 1.36 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.27 msec per loop

>>> python2 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 935 usec per loop

यह हुई ना बात! हम dequeपुनरावृति का उपयोग करके अभी भी थोड़ा तेज़ हो सकते हैं । यह मूल रूप से एक ही है, लेकिन यह तेज है :

>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop

>>> python3 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 805 usec per loop

>>> python2 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 438 usec per loop

मुझे जो प्रभावित करता है वह यह है कि यूनीकोड ​​बायट्रस्ट के साथ प्रतिस्पर्धी है। हम इसे स्पष्ट रूप से bytesऔर unicodeदोनों में जाँच कर सकते हैं :

  • bytes

    >>> python3 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)).encode("ascii") for _ in range(100000))' 'deque(iterable, maxlen=0)'                                                                    :(
    1000 loops, best of 3: 571 usec per loop
    
    >>> python3 -m timeit -s 'import random; from collections import deque; iterable =         [chr(random.randint(0, 127)).encode("ascii") for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 394 usec per loop
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127))                 for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 757 usec per loop
    
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable =         [chr(random.randint(0, 127))                 for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 438 usec per loop

    यहाँ आप पायथन 3 को वास्तव में पायथन 2 की तुलना में तेज़ देखते हैं ।

  • unicode

    >>> python3 -m timeit -s 'import random; from collections import deque; iterable = u"".join(   chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 800 usec per loop
    
    >>> python3 -m timeit -s 'import random; from collections import deque; iterable =         [   chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 394 usec per loop
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable = u"".join(unichr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 1.07 msec per loop
    
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable =         [unichr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 469 usec per loop

    फिर से, पायथन 3 तेज है, हालांकि यह उम्मीद की जानी है ( strपायथन 3 में बहुत ध्यान दिया गया है)।

वास्तव में, यह unicode- bytesअंतर बहुत छोटा है, जो प्रभावशाली है।

तो चलिए इस एक मामले का विश्लेषण करते हैं, यह देखते हुए कि यह मेरे लिए तेज़ और सुविधाजनक है:

>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop

>>> python3 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop

हम वास्तव में टिम पीटर के 10-बार-उत्कीर्ण जवाब से इंकार कर सकते हैं!

>>> foo = iterable[123]
>>> iterable[36] is foo
True

ये कोई नई वस्तु नहीं हैं!

लेकिन यह ध्यान देने योग्य है: अनुक्रमण लागत । अनुक्रमण में अंतर की संभावना होगी, इसलिए पुनरावृत्ति और बस सूचकांक को हटा दें:

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'iterable[123]'
10000000 loops, best of 3: 0.0397 usec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable[123]'
10000000 loops, best of 3: 0.0374 usec per loop

अंतर छोटा लगता है, लेकिन लागत का कम से कम आधा हिस्सा अधिक है:

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable; 123'
100000000 loops, best of 3: 0.0173 usec per loop

इसलिए गति का अंतर इसे दोष देने के लिए पर्याप्त है। मुझे लगता है।

तो सूची को इतनी तेज़ी से अनुक्रमित क्यों किया जा रहा है?

ठीक है, मैं उस पर आपके पास वापस आऊंगा, लेकिन मेरा अनुमान है कि इंटर्न स्ट्रिंग्स (या कैश्ड वर्ण यदि यह एक अलग तंत्र है) के लिए जांच के लिए नीचे है । यह इष्टतम की तुलना में कम तेज होगा। लेकिन मैं स्रोत की जाँच करूँगा (हालाँकि मैं C ... में सहज नहीं हूँ) :)।


तो यहाँ स्रोत है:

static PyObject *
unicode_getitem(PyObject *self, Py_ssize_t index)
{
    void *data;
    enum PyUnicode_Kind kind;
    Py_UCS4 ch;
    PyObject *res;

    if (!PyUnicode_Check(self) || PyUnicode_READY(self) == -1) {
        PyErr_BadArgument();
        return NULL;
    }
    if (index < 0 || index >= PyUnicode_GET_LENGTH(self)) {
        PyErr_SetString(PyExc_IndexError, "string index out of range");
        return NULL;
    }
    kind = PyUnicode_KIND(self);
    data = PyUnicode_DATA(self);
    ch = PyUnicode_READ(kind, data, index);
    if (ch < 256)
        return get_latin1_char(ch);

    res = PyUnicode_New(1, ch);
    if (res == NULL)
        return NULL;
    kind = PyUnicode_KIND(res);
    data = PyUnicode_DATA(res);
    PyUnicode_WRITE(kind, data, 0, ch);
    assert(_PyUnicode_CheckConsistency(res, 1));
    return res;
}

ऊपर से चलते हुए, हमारे पास कुछ जाँचें होंगी। ये उबाऊ हैं। फिर कुछ असाइनमेंट, जो उबाऊ भी होने चाहिए। पहली रोचक पंक्ति है

ch = PyUnicode_READ(kind, data, index);

लेकिन हमें उम्मीद है कि यह तेज़ है, क्योंकि हम इसे अनुक्रमित करके एक सन्निहित सी सरणी से पढ़ रहे हैं। परिणाम, ch256 से कम होगा, इसलिए हम कैश्ड वर्ण वापस कर देंगेget_latin1_char(ch)

तो हम चलेंगे (पहले चेक को छोड़ते हुए)

kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
return get_latin1_char(ch);

कहाँ पे

#define PyUnicode_KIND(op) \
    (assert(PyUnicode_Check(op)), \
     assert(PyUnicode_IS_READY(op)),            \
     ((PyASCIIObject *)(op))->state.kind)

(जो उबाऊ है, क्योंकि वाद-विवादों को डिबग में अनदेखा कर दिया जाता है [इसलिए मैं जांच सकता हूं कि वे तेज हैं] और ((PyASCIIObject *)(op))->state.kind)(मुझे लगता है) एक अप्रत्यक्ष और एक सी-लेवल कास्ट है);

#define PyUnicode_DATA(op) \
    (assert(PyUnicode_Check(op)), \
     PyUnicode_IS_COMPACT(op) ? _PyUnicode_COMPACT_DATA(op) :   \
     _PyUnicode_NONCOMPACT_DATA(op))

(जो समान कारणों से भी उबाऊ है, मैक्रोज़ मानते हुए ( Something_CAPITALIZEDसभी तेजी से हैं),

#define PyUnicode_READ(kind, data, index) \
    ((Py_UCS4) \
    ((kind) == PyUnicode_1BYTE_KIND ? \
        ((const Py_UCS1 *)(data))[(index)] : \
        ((kind) == PyUnicode_2BYTE_KIND ? \
            ((const Py_UCS2 *)(data))[(index)] : \
            ((const Py_UCS4 *)(data))[(index)] \
        ) \
    ))

(जिसमें इंडेक्स शामिल है लेकिन वास्तव में धीमा नहीं है) और

static PyObject*
get_latin1_char(unsigned char ch)
{
    PyObject *unicode = unicode_latin1[ch];
    if (!unicode) {
        unicode = PyUnicode_New(1, ch);
        if (!unicode)
            return NULL;
        PyUnicode_1BYTE_DATA(unicode)[0] = ch;
        assert(_PyUnicode_CheckConsistency(unicode, 1));
        unicode_latin1[ch] = unicode;
    }
    Py_INCREF(unicode);
    return unicode;
}

जो मेरे संदेह की पुष्टि करता है:

  • यह कैश्ड है:

    PyObject *unicode = unicode_latin1[ch];
  • यह उपवास होना चाहिए। if (!unicode)नहीं चला है, इसलिए यह करने के लिए इस मामले में सचमुच बराबर है

    PyObject *unicode = unicode_latin1[ch];
    Py_INCREF(unicode);
    return unicode;

ईमानदारी से, परीक्षण के बाद assertतेजी से कर रहे हैं (उन्हें अक्षम करके [मुझे लगता है कि यह सी-लेवल पर काम करता है ...]), केवल प्रशंसनीय-धीमी भागों हैं:

PyUnicode_IS_COMPACT(op)
_PyUnicode_COMPACT_DATA(op)
_PyUnicode_NONCOMPACT_DATA(op)

कौन से:

#define PyUnicode_IS_COMPACT(op) \
    (((PyASCIIObject*)(op))->state.compact)

(उपवास, पहले की तरह),

#define _PyUnicode_COMPACT_DATA(op)                     \
    (PyUnicode_IS_ASCII(op) ?                   \
     ((void*)((PyASCIIObject*)(op) + 1)) :              \
     ((void*)((PyCompactUnicodeObject*)(op) + 1)))

(तेजी से अगर मैक्रो IS_ASCIIतेज है), और

#define _PyUnicode_NONCOMPACT_DATA(op)                  \
    (assert(((PyUnicodeObject*)(op))->data.any),        \
     ((((PyUnicodeObject *)(op))->data.any)))

(यह भी तेजी के रूप में यह एक जोर प्लस एक परोक्ष प्लस एक डाली है)।

तो हम नीचे हैं (खरगोश छेद):

PyUnicode_IS_ASCII

जो है

#define PyUnicode_IS_ASCII(op)                   \
    (assert(PyUnicode_Check(op)),                \
     assert(PyUnicode_IS_READY(op)),             \
     ((PyASCIIObject*)op)->state.ascii)

हम्म ... जो तेजी से भी लगता है ...


ठीक है, ठीक है, लेकिन इसकी तुलना करते हैं PyList_GetItem। (हाँ, मुझे काम करने के लिए और अधिक काम देने के लिए धन्यवाद टिम पीटर्स: पी।)

PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
    if (!PyList_Check(op)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    if (i < 0 || i >= Py_SIZE(op)) {
        if (indexerr == NULL) {
            indexerr = PyUnicode_FromString(
                "list index out of range");
            if (indexerr == NULL)
                return NULL;
        }
        PyErr_SetObject(PyExc_IndexError, indexerr);
        return NULL;
    }
    return ((PyListObject *)op) -> ob_item[i];
}

हम देख सकते हैं कि गैर-त्रुटि मामलों पर यह बस चलने वाला है:

PyList_Check(op)
Py_SIZE(op)
((PyListObject *)op) -> ob_item[i]

कहाँ PyList_Checkहै

#define PyList_Check(op) \
     PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS)

( TABS! TABS !!! ) ( अंक २१५ That ) जो तय हो गया और ५ मिनट में विलीन हो गया । जैसे ... हाँ। अरे नहीं। उन्होंने शर्म करने के लिए स्कीट डाल दिया।

#define Py_SIZE(ob)             (((PyVarObject*)(ob))->ob_size)
#define PyType_FastSubclass(t,f)  PyType_HasFeature(t,f)
#ifdef Py_LIMITED_API
#define PyType_HasFeature(t,f)  ((PyType_GetFlags(t) & (f)) != 0)
#else
#define PyType_HasFeature(t,f)  (((t)->tp_flags & (f)) != 0)
#endif

तो यह आम तौर पर तुच्छ (दो अप्रत्यक्ष और बूलियन जांच के एक जोड़े) जब तक Py_LIMITED_APIहै, किस मामले में है ... ???

फिर अनुक्रमण और एक कास्ट ( ((PyListObject *)op) -> ob_item[i]) है और हम कर रहे हैं।

इसलिए सूचियों के लिए निश्चित रूप से कम जांच होती है, और छोटे गति अंतर निश्चित रूप से इसका मतलब है कि यह प्रासंगिक हो सकता है।


मुझे लगता है कि सामान्य रूप से (->), यूनिकोड के लिए बस अधिक प्रकार की जाँच और अप्रत्यक्ष है। ऐसा लगता है कि मुझे एक बिंदु याद आ रहा है, लेकिन क्या ?


17
आप कोड को आत्म व्याख्यात्मक के रूप में प्रस्तुत कर रहे हैं; आप निष्कर्ष के रूप में स्निपेट भी प्रस्तुत कर रहे हैं। दुर्भाग्य से मेरे लिए, मैं वास्तव में इसका पालन नहीं कर सकता। यह कहते हुए कि गलत क्या है, यह पता लगाने के लिए आपका दृष्टिकोण ठोस नहीं है, लेकिन यह अच्छा होगा यदि इसका पालन करना आसान हो।
पास्कलवूटन

2
मैंने इसे सुधारने की कोशिश की, लेकिन मैं अनिश्चित हूं कि इसे कैसे स्पष्ट किया जाए। ध्यान दें कि मैं C नहीं लिखता, इसलिए यह कोड का एक उच्च-स्तरीय विश्लेषण है और केवल समग्र अवधारणाएं महत्वपूर्ण हैं।
विड्रैक

@ मैंने जोड़ा है। अगर कमी महसूस हो तो मुझे बताना। दुर्भाग्य से यह भी उजागर करता है कि मैं वास्तव में उत्तर (* हांसी *) नहीं जानता।
विड्रैक

3
आपका उत्तर स्वीकार करने से पहले मैं इसे एक और दिन दूंगा (मैं कुछ और ठोस पॉप देखना पसंद करूंगा), लेकिन बहुत दिलचस्प और अच्छी तरह से शोध किए गए उत्तर के लिए धन्यवाद।
सुंजय वर्मा

4
ध्यान दें कि आप एक चलती लक्ष्य पर शूटिंग कर रहे हैं ;-) यह कार्यान्वयन केवल पायथन 2 और पायथन 3 के बीच ही नहीं, बल्कि विभिन्न रिलीज़ के बीच भी भिन्न है। उदाहरण के लिए, वर्तमान विकास ट्रंक पर, get_latin1_char()चाल अब मौजूद नहीं है unicode_getitem(), लेकिन निचले-स्तर में है unicode_char। इसलिए अब फंक्शन कॉल का एक और स्तर है - या नहीं (उपयोग किए गए कंपाइलर और ऑप्टिमाइज़ेशन फ्लैग के आधार पर)। विस्तार के इस स्तर पर, बस कोई विश्वसनीय उत्तर नहीं हैं ;-)
टिम पीटर्स

31

जब आप अधिकांश कंटेनर ऑब्जेक्ट्स (सूचियों, टुपल्स, डाइचेस, ...) पर पुनरावृति करते हैं, तो इट्रेटर ऑब्जेक्ट्स को अंदर पहुंचाता है कंटेनर ।

लेकिन जब आप एक स्ट्रिंग पर पुनरावृति करते हैं, तो वितरित किए गए प्रत्येक वर्ण के लिए एक नई वस्तु बनानी होगी - एक स्ट्रिंग "कंटेनर" नहीं है उसी अर्थ में एक सूची एक कंटेनर है। स्ट्रिंग में अलग-अलग वर्ण मौजूद नहीं हैं क्योंकि पुनरावृत्ति से पहले उन वस्तुओं का निर्माण होता है।


3
मुझे नहीं लगता कि यह वास्तव में सच है। आप के साथ जाँच कर सकते हैं is। यह सही लगता है, लेकिन मुझे नहीं लगता कि यह हो सकता है।
विड्रैक

@Veedrac उत्तर पर एक नज़र डालें।
ईसाई

3
stringobject.cदिखाता है कि __getitem__स्ट्रिंग्स केवल संग्रहीत 1-वर्ण स्ट्रिंग्स की तालिका से परिणाम प्राप्त करता है, इसलिए उन के लिए आवंटन लागत केवल एक बार खर्च होती है।
user2357112 मोनिका

10
@ user2357112, हाँ, पायथन 2 में सादे तार के लिए यह एक महत्वपूर्ण बिंदु है। पायथन 3 में, सभी स्ट्रिंग्स "आधिकारिक तौर पर" यूनिकोड हैं और बहुत अधिक विवरण शामिल हैं (देखें Veedrac का उत्तर)। उदाहरण के लिए, पायथन 3 में, के बाद s = chr(256), s is chr(256)रिटर्न False- अकेले प्रकार को जानना पर्याप्त नहीं है, क्योंकि डेटा मानों पर ट्रिगर होने वाले कवर के तहत विशेष मामलों के टीले मौजूद हैं
टिम पीटर्स

1

आप स्ट्रिंग के लिए पुनरावृत्ति बनाने के लिए भड़काने और ओवरहेड हो सकते हैं। जबकि सरणी में तात्कालिकता पर पहले से ही एक पुनरावृत्त होता है।

संपादित करें:

>>> timeit("[x for x in ['a','b','c']]")
0.3818681240081787
>>> timeit("[x for x in 'abc']")
0.3732869625091553

यह 2.7 का उपयोग करके चलाया गया था, लेकिन मेरी मैक बुक प्रो i7 पर। यह एक सिस्टम कॉन्फ़िगरेशन अंतर का परिणाम हो सकता है।


यहां तक ​​कि सिर्फ सीधे चलने वाले का उपयोग करके, स्ट्रिंग अभी भी काफी धीमी है। timeit ("[इसमें x के लिए x]", "it = iter ('abc')") = 0.34543599384033535; timeit ("[इसमें x के लिए x]", "it = iter (सूची ('abc'))") = 0.2791691380446508
Varma
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.