Neden küçük bir dizgeyi yinelemek küçük bir listeden daha yavaş?


132

Zamanla oynuyordum ve küçük bir dizge üzerinde basit bir liste kavrama yapmanın, aynı işlemi küçük tek karakter dizileri listesinde yapmaktan daha uzun sürdüğünü fark ettim. Herhangi bir açıklama var mı? Neredeyse 1,35 katı zaman.

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

Buna neden olan daha düşük bir seviyede neler oluyor?

Yanıtlar:


193

TL; DR

  • Python 2 için, ek yükün çoğu kaldırıldığında gerçek hız farkı% 70'e (veya daha fazlasına) yakındır.

  • Nesne oluşturma hatalı değildir . Tek karakterli dizeler önbelleğe alındığı için hiçbir yöntem yeni bir nesne oluşturmaz.

  • Aradaki fark açık değildir, ancak büyük olasılıkla tür ve iyi biçim açısından dizi indekslemede daha fazla sayıda denetimden kaynaklanmaktadır. Neyin iade edileceğini kontrol etme ihtiyacı da oldukça muhtemeldir.

  • Liste indeksleme oldukça hızlıdır.



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

Bu bulduğunuz şeyle uyuşmuyor ...

O halde Python 2 kullanıyor olmalısın.

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

Sürümler arasındaki farkı açıklayalım. Derlenmiş kodu inceleyeceğim.

Python 3 için:

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

Burada, her seferinde listenin oluşturulması nedeniyle liste varyantının daha yavaş olacağını görüyorsunuz.

Bu

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

Bölüm. Dize varyantında yalnızca

 9 LOAD_CONST   3 ('abc')

Bunun bir fark yarattığını kontrol edebilirsiniz:

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

Bu sadece üretir

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

tuplelar değişmez olduğu için. Ölçek:

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

Harika, hızınızı artırın.

Python 2 için:

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        

Garip olan şey, listenin aynı yapısına sahip olmamız , ancak bunun için hala daha hızlı. Python 2 garip bir şekilde hızlı davranıyor.

Anlamaları ve yeniden zamanı kaldıralım. Bu _ =, optimize edilmesini önlemektir.

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

Başlatma işleminin sürümler arasındaki farkı hesaba katacak kadar önemli olmadığını görebiliriz (bu sayılar küçüktür)! Böylece Python 3'ün daha yavaş kavrayışa sahip olduğu sonucuna varabiliriz. Python 3, daha güvenli kapsama sahip olmak için anlamaları değiştirdiği için bu mantıklıdır.

Şimdi kıyaslamayı iyileştirin (sadece yineleme olmayan genel giderleri kaldırıyorum). Bu, yinelenebilirin yapısını önceden atayarak kaldırır:

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

iterAramanın ek yük olup olmadığını kontrol edebiliriz :

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

Hayır. Hayır değil. Fark, özellikle Python 3 için çok küçük.

Öyleyse, her şeyi yavaşlatarak daha fazla istenmeyen yükü kaldıralım! Amaç sadece daha uzun bir yinelemeye sahip olmak, böylece zamanın üstte gizlenmesi.

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

Bu aslında pek değişmedi ama biraz yardımı oldu.

Öyleyse anlayışı kaldırın. Sorunun bir parçası olmayan genel giderler:

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

Bu daha doğru gibi! dequeYinelemeyi kullanarak biraz daha hızlı olabiliriz . Temelde aynı, ancak daha hızlı :

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

Beni etkileyen şey, Unicode'un bytestrings ile rekabet edebilmesi. Bunu deneyerek bytesve unicodeher ikisinde de açıkça kontrol edebiliriz :

  • 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

    Burada Python 3'ü aslında Python 2'den daha hızlı görüyorsunuz .

  • 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

    Yine, Python 3 daha hızlıdır, ancak bu beklenmelidir ( strPython 3'te çok dikkat çekmiştir).

Aslında, bu unicode- bytesfark çok küçük ve bu etkileyici.

Öyleyse, benim için hızlı ve kullanışlı olduğunu görerek bu durumu analiz edelim:

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

Tim Peter'ın 10 kez olumlu oy verilen cevabını gerçekten göz ardı edebiliriz!

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

Bunlar yeni nesneler değil!

Ancak şunu belirtmeye değer: endeksleme maliyetleri . Fark büyük olasılıkla dizine eklemede olacaktır, bu nedenle yinelemeyi kaldırın ve yalnızca dizine ekleyin:

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

Fark küçük görünüyor, ancak maliyetin en az yarısı genel giderler:

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

bu yüzden hız farkı onu suçlamaya karar vermek için yeterlidir. Bence.

Öyleyse neden bir listeyi indekslemek çok daha hızlı?

Pekala, size bu konuda geri döneceğim, ancak benim tahminim bu, dahili dizeleri (veya ayrı bir mekanizma ise önbelleğe alınmış karakterleri) kontrol etmeye bağlı. Bu, optimumdan daha az hızlı olacaktır. Ama kaynağa bakacağım (C ... konusunda rahat olmasam da) :).


İşte kaynak:

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

Yukarıdan yürürken, bazı kontrollerimiz olacak. Bunlar sıkıcı. Sonra da sıkıcı olması gereken bazı ödevler. İlk ilginç satır

ch = PyUnicode_READ(kind, data, index);

ancak bitişik bir C dizisini indeksleyerek okuduğumuz için bunun hızlı olmasını umuyoruz . Sonuç, ch256'dan az olacak, bu nedenle önbelleğe alınan karakteri içinde döndüreceğiz get_latin1_char(ch).

Öyleyse koşacağız (ilk kontrolleri bırakacağız)

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

Nerede

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

(ki bu sıkıcı çünkü iddialar hata ayıklamada göz ardı ediliyor [böylece hızlı olduklarını kontrol edebilirim] ve ((PyASCIIObject *)(op))->state.kind)(bence) bir dolaylı ve C-seviyesi döküm);

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

(makroların ( Something_CAPITALIZED) hepsinin hızlı olduğunu varsayarak, benzer nedenlerden dolayı da sıkıcıdır ),

#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)] \
        ) \
    ))

(dizinler içerir ancak gerçekten yavaş değildir) ve

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

Bu şüphelerimi doğruluyor:

  • Bu önbelleğe alınır:

    PyObject *unicode = unicode_latin1[ch];
  • Bu hızlı olmalı. if (!unicode)O kadar bu durumda tam anlamıyla eşdeğer yüzden, koşmak değildir

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

Dürüst olmak gerekirse, s'lerin asserthızlı olduğunu test ettikten sonra (onları devre dışı bırakarak [ C düzeyindeki iddialarda çalıştığını düşünüyorum ...]), makul derecede yavaş olan tek kısımlar:

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

Hangileri:

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

(eskisi gibi hızlı),

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

(makro IS_ASCIIhızlıysa hızlı) ve

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

(aynı zamanda bir iddia artı bir dolaylama artı bir döküm olduğu için hızlı).

Öyleyse aşağıdayız (tavşan deliğinden):

PyUnicode_IS_ASCII

hangisi

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

Hmm ... bu da hızlı görünüyor ...


Peki, tamam, ama bunu karşılaştıralım PyList_GetItem. (Evet, Tim Peters'a yapmam için daha çok iş verdiği için teşekkürler : S.)

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];
}

Hata olmayan durumlarda bunun sadece çalışacağını görebiliriz:

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

nerede PyList_Checkolduğunu

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

( SEKMELER ! SEKMELER !!! ) ( sorun21587 ) Bu 5 dakika içinde düzeltildi ve birleştirildi . Mesela ... evet. Lanet olsun. Skeet'i utandırdılar.

#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

Yani bu normalde gerçekten önemsizdir (iki yönlendirme ve birkaç boole kontrolü), eğer Py_LIMITED_APIaçık değilse , bu durumda ... ???

Sonra indeksleme ve bir oyuncu ( ((PyListObject *)op) -> ob_item[i]) var ve işimiz bitti.

Bu yüzden listeler için kesinlikle daha az kontrol vardır ve küçük hız farkları kesinlikle bunun alakalı olabileceğini ima eder.


Bence genel olarak (->), Unicode için yalnızca daha fazla tür denetimi ve indirme var. Görünüşe göre bir noktayı kaçırıyorum, ama ne ?


17
Kodu kendinden açıklamalı olarak sunuyorsunuz; hatta parçacıkları sonuç olarak sunuyorsunuz. Ne yazık ki benim için gerçekten takip edemiyorum. Neyin yanlış olduğunu bulma yaklaşımınızın sağlam olmadığını söylemiyorum, ancak takip etmesi daha kolay olsaydı iyi olurdu.
PascalVKooten

2
İyileştirmeyi denedim, ancak nasıl daha net hale getireceğime emin değilim. C yazmadığımı unutmayın, bu yüzden bu kodun üst düzey bir analizi ve yalnızca genel kavramlar önemlidir.
Veedrac

@Nit ekledim. Bana eksik geliyorsa söyle. Maalesef, cevabı gerçekten bilmediğimi de vurguluyor (* gasp *).
Veedrac

3
Cevabınızı kabul etmeden önce bunu bir gün daha vereceğim (daha somut bir şey görmek isterim), ancak çok ilginç ve iyi araştırılmış cevap için teşekkür ederim.
Sunjay Varma

4
Hareket eden bir hedefe ateş ettiğinizi unutmayın ;-) Bu uygulama sadece Python 2 ve Python 3 arasında değil, aynı zamanda farklı sürümler arasında da farklılık gösterir. Örneğin, mevcut geliştirme gövdesinde, get_latin1_char()numara artık içinde unicode_getitem()değil, daha düşük seviyede var unicode_char. Yani şimdi başka bir fonksiyon çağrısı seviyesi var - ya da yok (kullanılan derleyiciye ve optimizasyon bayraklarına bağlı olarak). Bu ayrıntı düzeyinde, güvenilir cevaplar yoktur ;-)
Tim Peters

31

Eğer üzerinde en fazla konteyner nesneleri yineleme zaman (listeler, küpe, dicts, ...), yineleyici nesneleri teslim içinde kap.

Ancak bir dizge üzerinde yineleme yaptığınızda, teslim edilen her karakter için yeni bir nesne oluşturulmalıdır - bir dizge, aynı anlamda "bir kap" değildir, bir liste bir kaptır. Bir dizedeki tek tek karakterler, yineleme bu nesneleri oluşturmadan önce ayrı nesneler olarak var olmaz.


3
Aslında bunun doğru olduğunu sanmıyorum. İle kontrol edebilirsiniz is. Bu sesler sağ, ama gerçekten o olabilir sanmıyorum.
Veedrac

@Veedrac cevabına bir göz atın.
Christian

3
stringobject.c__getitem__dizeler için, saklanan 1 karakterlik dizilerin bir tablosundan sonucu aldığını gösterir , bu nedenle bunlar için ayırma maliyetleri yalnızca bir kez yapılır.
user2357112, Monica'yı

10
@ user2357112, evet, Python 2'deki düz dizeler için bu hayati bir nokta. Python 3'te, tüm dizeler "resmi olarak" Unicode'dur ve çok daha fazla ayrıntı söz konusudur (bkz. Veedrac'ın cevabı). Örneğin, Python 3'te, sonra s = chr(256), s is chr(256)döner False- Özel durumlarda höyükleri veri üzerinde tetikleme yorganın altında bulunduğu için yalnız türünü bilmek, yeterli değildir değerler .
Tim Peters

1

Dize için yineleyici oluşturmak için yükümlü ve ek yük olabilirsiniz. Halbuki dizi, somutlaştırma üzerine zaten bir yineleyici içeriyor.

DÜZENLE:

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

Bu 2.7 kullanılarak çalıştırıldı, ancak mac book pro i7'mde. Bu, bir sistem konfigürasyon farkının sonucu olabilir.


Sadece düz yineleyiciler kullanıldığında bile, dizge hala önemli ölçüde daha yavaştır. timeit ("[içindeki x için x]", "it = iter ('abc')") = 0,34543599384033535; timeit ("[x içindeki x için]", "it = iter (list ('abc'))") = 0.2791691380446508
Sunjay Varma
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.