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




performance cpython (3)

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

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

निम्न स्तर पर क्या हो रहा है जो इसका कारण बन रहा है?


टी एल; डॉ

  • पाइथन 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 से 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 की तुलना में पाइथन 3 वास्तव में तेज़ी से देखते हैं।

  • 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

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

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

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

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

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);

लेकिन हम उम्मीद करेंगे कि यह तेज़ है, क्योंकि हम इसे अनुक्रमित करके एक संगत सी सरणी से पढ़ रहे हैं। नतीजा, ch , 256 से कम होगा, इसलिए हम 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) को नजरअंदाज कर दिया जाता है [इसलिए मैं जांच सकता हूं कि वे तेज़ हैं] और ((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)

( टैब्स! टैब !!! ) ( issue21587 ) यह तय हो गया और 5 मिनट में विलय हो गया। पसंद है ... हाँ। अरे नहीं। उन्होंने स्कीट को शर्मिंदा कर दिया।

#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 और दो बूलियन चेक) होता है जब तक कि Py_LIMITED_API न हो, तो किस मामले में ... ???

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

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

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


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

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


स्ट्रिंग के लिए इटरेटर बनाने के लिए आप खर्च और ओवरहेड हो सकते हैं। जबकि सरणी में पहले से ही त्वरण पर एक इटरेटर शामिल है।

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

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

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






python-internals