programing

작은 목록보다 작은 문자열에 대해 반복하는 것이 느린 이유는 무엇입니까?

bestprogram 2023. 8. 5. 10:46

작은 목록보다 작은 문자열에 대해 반복하는 것이 느린 이유는 무엇입니까?

저는 그것을 가지고 놀다가 작은 문자열에 대한 간단한 목록 이해를 하는 것이 작은 단일 문자열 목록에서 같은 작업을 하는 것보다 더 오래 걸린다는 것을 깨달았습니다.무슨 설명이라도?그것은 거의 1.35배의 시간입니다.

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

더 낮은 단계에서 무슨 일이 일어나고 있으며 이로 인해 발생하는 것입니까?

TL;DR

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

버전 간의 차이점을 설명하겠습니다.컴파일된 코드를 검토하겠습니다.

Python 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

part. 문자열 변형은 다음을 포함합니다.

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

불변의 예법테스트:

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

좋아요, 속도를 내세요.

Python 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        

이상한 것은 우리가 목록의 같은 건물을 가지고 있다는 것입니다. 하지만 이것은 여전히 더 빠릅니다.Python 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

초기화가 버전 간의 차이를 설명할 만큼 충분히 중요하지 않다는 것을 알 수 있습니다(이러한 숫자는 작습니다).따라서 Python 3의 이해가 더 느리다는 결론을 내릴 수 있습니다.이것은 Python 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

아니요, 아닙니다특히 Python 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
    

    입니다. (Python 3, Python 3)strPython 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);

하지만 색인화를 통해 연속적인 C 배열을 읽고 있기 때문에 속도가 빠르기를 바랍니다.결과는,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)

debug [할 수 asserts에서 무시되기 .((PyASCIIObject *)(op))->state.kind)생각에는) 캐스트야 (C급 캐스트야) C급 캐스트야.

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

솔직히, 테스트 후에.asserts는 빠릅니다([C-level asserts에서 작동하는 것 같습니다...]). 유일하게 느린 부분은 다음과 같습니다.

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(네, 피터스가 저에게 더 많은 일을 주셔서 감사합니다:P.)

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)

( 탭! 탭!!!) ( 87호) 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켜져 있어요, 어떤 경우에... ???

그리고 인덱싱과 캐스트가 있습니다.((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을 사용하여 실행되었지만, 제 맥북 프로7에서 실행되었습니다.이는 시스템 구성 차이의 결과일 수 있습니다.

언급URL : https://stackoverflow.com/questions/23861468/why-is-it-slower-to-iterate-over-a-small-string-than-a-small-list