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
이것은 당신이 찾은 것에 동의하지 않습니다 ...
그렇다면 Python 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'))
튜플은 불변이므로 테스트:
>>> 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이 더 안전한 범위를 갖도록 이해를 변경함에 따라 의미가 있습니다.
이제 벤치 마크를 개선하십시오 (반복되지 않는 오버 헤드 만 제거함). 이것은 iterable의 건물을 미리 할당하여 제거합니다.
>>> 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
다시 말하지만, 파이썬 3은 더 빠르지 만, 파이썬 3 str
에서는 많은주의를 기울였습니다.
사실,이 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
우리는 실제로 Tim Peter의 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
속도 차이는 그것을 비난하기에 충분합니다. 내 생각에
그렇다면 왜 목록을 훨씬 빨리 색인화합니까?
글쎄, 나는 그것에 대해 당신에게 돌아올 것이지만, 내 추측은 interned 문자열 (또는 별도의 메커니즘 인 경우 캐시 된 문자)을 확인하는 것입니다. 이것은 최적보다 빠르지 않습니다. 그러나 나는 소스를 검사 할 것입니다 (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 배열에서 읽을 때 빠른 속도를 기대 합니다. 결과 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)
) 간접적이며 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;
솔직히 말해서 assert
s가 빠르면 ([ C 레벨 주장에서 작동 한다고 생각 합니다 ...]), 느리게 느린 부분은 다음과 같습니다.
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)
( TABS! TABS !!! ) ( 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
.
그런 다음 색인 생성과 캐스트 ( ((PyListObject *)op) -> ob_item[i]
)가 완료되었습니다.
따라서 목록에 대한 검사 가 확실히 적 으며, 작은 속도 차이로 인해 관련성이있을 수 있습니다.
나는 일반적으로 (->)
유니 코드에 대한 유형 검사와 간접 지시가 더 있다고 생각 합니다. 내가 요점을 놓친 것 같지만 무엇 ?