python - 함수 - []가 list()보다 빠른 이유는 무엇입니까?
파이썬 title 함수 (4)
최근에
[]
와
list()
의 처리 속도를 비교 한 결과
[]
가
list()
보다 3 배
이상
빠르다
는 사실에 놀랐습니다.
{}
와
dict()
사용하여 동일한 테스트를 실행했으며 결과는 실제로 동일했습니다.
[]
와
{}
모두 약 0.128 초 / 백만주기를 사용했지만
list()
와
dict()
는 각각 약 0.428 초 / 백만주기를 사용했습니다.
왜 이런거야?
[]
와
{}
(그리고 아마도
()
와
''
도)는 명시 적으로 이름이 지정된 상대 (
list()
,
dict()
,
tuple()
,
str()
) 동안 빈 주식 리터럴의 복사본을 즉시 전달합니다. 실제로 요소가 있는지 여부에 관계없이 객체를 만드는 방법에 대해 완전히 다룰까요?
이 두 가지 방법이 어떻게 다른지 잘 모르겠지만 알고 싶습니다. 문서 나 SO에서 답을 찾을 수 없었으며 빈 괄호를 검색하는 것이 예상보다 문제가 많은 것으로 나타났습니다.
timeit.timeit("[]")
및
timeit.timeit("list()")
,
timeit.timeit("{}")
및
timeit.timeit("dict()")
을 호출하여 타이밍 결과를 얻었습니다. 목록과 사전을 각각 비교합니다.
Python 2.7.9를 실행 중입니다.
나는 최근에 " 참이 1보다 느리면 왜? "라는 사실을 발견했습니다. 이것은 사실 1의 경우와 1 의 성능을 비교하고 유사한 문자 대 글로벌 시나리오를 다루는 것 같습니다. 아마도 고려해 볼 가치가 있습니다.
[]
가list()
보다 빠른 이유는 무엇입니까?
가장 큰 이유는 파이썬이
list()
사용자 정의 함수처럼 취급하기 때문입니다. 즉,
list
다른 것을 별칭으로 지정하여 자신의 하위 클래스 목록이나 deque를 사용하는 것과 같이 다른 것을 수행하여 가로 챌 수 있습니다.
[]
로 내장리스트의 새로운 인스턴스를 즉시 생성합니다.
나의 설명은 당신에게 이것에 대한 직감을 제공하려고합니다.
설명
[]
는 일반적으로 리터럴 구문으로 알려져 있습니다.
문법에서는이를 "목록 표시"라고합니다. 문서에서 :
목록 표시는 대괄호로 묶인 빈 일련의 표현식입니다.
list_display ::= "[" [starred_list | comprehension] "]"
목록 표시는 새 목록 객체를 생성하며, 내용은 표현식 목록 또는 이해로 지정됩니다. 쉼표로 구분 된 표현식 목록이 제공되면 해당 요소가 왼쪽에서 오른쪽으로 평가되어 순서대로 목록 오브젝트에 배치됩니다. 이해가 제공 될 때,리스트는 이해의 결과로 구성되는 요소들로 구성됩니다.
즉,
list
유형의 내장 객체가 생성됨을 의미합니다.
이것을 피할 수는 없습니다. 즉, 파이썬은 최대한 빨리 할 수 있습니다.
반면에,
list()
는 내장리스트 생성자를 사용하여 내장리스트를 생성하는 것을 가로 챌 수 있습니다.
예를 들어, 목록이 시끄럽게 생성되기를 원한다고 가정 해보십시오.
class List(list):
def __init__(self, iterable=None):
if iterable is None:
super().__init__()
else:
super().__init__(iterable)
print('List initialized.')
그런 다음 모듈 수준 전역 범위에서 이름
list
을 가로 챌 수 있으며,
list
을 만들 때 실제로 하위 유형
list
을 만듭니다.
>>> list = List
>>> a_list = list()
List initialized.
>>> type(a_list)
<class '__main__.List'>
마찬가지로 전역 네임 스페이스에서 제거 할 수 있습니다.
del list
내장 네임 스페이스에 넣습니다.
import builtins
builtins.list = List
그리고 지금:
>>> list_0 = list()
List initialized.
>>> type(list_0)
<class '__main__.List'>
그리고리스트 디스플레이는 무조건리스트를 만듭니다.
>>> list_1 = []
>>> type(list_1)
<class 'list'>
아마도이 작업은 일시적으로 만 수행되므로 변경 사항을 취소하십시오. 먼저 내장에서 새
List
객체를 제거하십시오.
>>> del builtins.list
>>> builtins.list
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'builtins' has no attribute 'list'
>>> list()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'list' is not defined
아뇨, 우리는 원본을 잃어 버렸습니다.
걱정하지 않아도
list
얻을 수 있습니다.리스트 리터럴의 유형입니다.
>>> builtins.list = type([])
>>> list()
[]
그래서...
[]
가list()
보다 빠른 이유는 무엇입니까?
우리가 보았 듯이
list
을 덮어 쓸 수는 있지만 리터럴 유형의 생성을 가로 챌 수는 없습니다.
list
를 사용할 때 어떤 것이 있는지 확인하기 위해 조회를해야합니다.
그런 다음 우리가 찾은 호출 가능한 것을 호출해야합니다. 문법에서 :
호출은 일련의 빈 인수로 호출 가능한 객체 (예 : 함수)를 호출합니다.
call ::= primary "(" [argument_list [","] | comprehension] ")"
우리는 목록뿐만 아니라 모든 이름에 대해 동일한 작업을 수행한다는 것을 알 수 있습니다.
>>> import dis
>>> dis.dis('list()')
1 0 LOAD_NAME 0 (list)
2 CALL_FUNCTION 0
4 RETURN_VALUE
>>> dis.dis('doesnotexist()')
1 0 LOAD_NAME 0 (doesnotexist)
2 CALL_FUNCTION 0
4 RETURN_VALUE
[]
경우 Python 바이트 코드 레벨에서 함수 호출이 없습니다.
>>> dis.dis('[]')
1 0 BUILD_LIST 0
2 RETURN_VALUE
바이트 코드 수준에서 조회하거나 호출하지 않고 목록을 작성하는 것입니다.
결론
범위 지정 규칙을 사용하여
list
을 사용자 코드로 가로 챌 수 있고 해당
list()
가 호출 가능 항목을 찾은 다음 호출합니다.
[]
는 목록 표시 또는 리터럴이므로 이름 조회 및 함수 호출을 피합니다.
이 질문에 대한 대답은 훌륭하고이 질문을 완전히 다루고 있습니다. 관심있는 사람들을 위해 바이트 코드에서 한 단계 더 나아가겠습니다. CPython의 최신 저장소를 사용하고 있습니다. 이전 버전은 이와 관련하여 비슷하게 작동하지만 약간의 변경이있을 수 있습니다.
다음은 각각의 실행에 대한
BUILD_LIST
입니다.
[]
대한
CALL_FUNCTION
및
list()
대한
CALL_FUNCTION
.
BUILD_LIST
명령어 :
당신은 공포를 볼 수 있습니다 :
PyObject *list = PyList_New(oparg);
if (list == NULL)
goto error;
while (--oparg >= 0) {
PyObject *item = POP();
PyList_SET_ITEM(list, oparg, item);
}
PUSH(list);
DISPATCH();
몹시 혼란 스러웠습니다. 이것이 얼마나 간단합니다 :
-
PyList_New
하여 새 목록을 작성하십시오 (주로 새 목록 오브젝트에 메모리를 할당 함).oparg
는 스택의 인수 수를 표시합니다. 바로 포인트. -
if (list==NULL)
아무 문제가 없는지 확인하십시오. -
PyList_SET_ITEM
(매크로)을 사용하여 스택에있는 인수 (이 경우에는 실행되지 않음)를 추가하십시오.
빠르다는 것은 놀라운 일이 아닙니다! 새로운 목록을 만들기 위해 맞춤 제작되었습니다.
CALL_FUNCTION
명령어 :
CALL_FUNCTION
처리 코드를
CALL_FUNCTION
때 가장 먼저 보게되는 것은 다음과 같습니다.
PyObject **sp, *res;
sp = stack_pointer;
res = call_function(&sp, oparg, NULL);
stack_pointer = sp;
PUSH(res);
if (res == NULL) {
goto error;
}
DISPATCH();
무해 해 보이죠?
글쎄, 불행히도
call_function
은 함수를 즉시 호출하는 간단한 사람이 아닙니다.
대신 스택에서 객체를 가져오고 스택의 모든 인수를 가져온 다음 객체 유형에 따라 전환합니다.
그건:
-
PyCFunction_Type
? 아니요,list
.list
는PyCFunction
유형이PyCFunction
-
PyMethodType
? 아니요, 이전을 참조하십시오. -
PyFunctionType
? 노피, 이전 참조.
list
타입을 호출하고 있습니다.
PyList_Type
전달 된 인수는
PyList_Type
입니다.
CPython은 이제
_PyObject_FastCallKeywords
라는 호출 가능한 객체를 처리하기 위해 일반 함수를 호출해야합니다.
이 함수는 특정 함수 유형 (이유를 이해할 수 없음)을 다시 확인한 다음,
필요한 경우
kwargs에 대한 사전을
_PyObject_FastCallDict
를 호출
_PyObject_FastCallDict
.
_PyObject_FastCallDict
마침내 우리를 어딘가에
_PyObject_FastCallDict
줍니다!
더 많은 검사를
수행 한 후에는 전달한
type
의
type
에서
tp_call
슬롯을 가져
type.tp_call
. 즉,
type.tp_call
.
그런 다음
_PyStack_AsTuple
과 함께 전달 된 인수에서 튜플을 작성하고 마지막
으로 호출을 할 수 있습니다
!
tp_call
은
type.__call__
과 일치하여 목록 객체를 생성합니다.
PyType_GenericNew
해당하는
__new__
목록을 호출하고
PyType_GenericNew
하여 메모리를 할당합니다.
실제로
PyList_New
따라 잡는 부분
입니다.
이전의 모든 것은 일반적인 방식으로 객체를 처리하는 데 필요합니다.
결국
type_call
은
list.__init__
type_call
호출하고 사용 가능한 인수로 목록을 초기화 한 다음 우리가 왔던 방식으로 되돌아갑니다.
:-)
마지막으로, LOAD_NAME을
LOAD_NAME
하십시오. 여기에 기여하는 또 다른 사람입니다.
입력을 다룰 때 파이썬은 일반적으로 작업을 수행하기에 적합한
C
함수를 실제로 찾기 위해 농구대를 뛰어 넘어야한다는 것을 쉽게 알 수 있습니다.
역동적이고 누군가가
list
숨기고 (
그리고 많은 사람들이하는 많은 소년
) 다른 경로를 취해야하기 때문에 즉시 호출하는 curtey가 없습니다.
이것은
list()
가 많이 잃는 곳입니다. 탐험하는 파이썬은 도대체 무엇을해야하는지 알아 내야합니다.
반면에 리터럴 구문은 정확히 한 가지를 의미합니다. 변경 될 수 없으며 항상 미리 결정된 방식으로 작동합니다.
각주 : 모든 기능 이름은 릴리스마다 변경 될 수 있습니다. 요점은 여전히 스탠드되며 향후 버전에서는 대부분 가능성이 높습니다. 이것은 속도를 늦추는 동적 조회입니다.
list()
에는 전역 조회와 함수 호출이 필요하지만
[]
는 단일 명령어로 컴파일됩니다.
만나다:
Python 2.7.3
>>> import dis
>>> print dis.dis(lambda: list())
1 0 LOAD_GLOBAL 0 (list)
3 CALL_FUNCTION 0
6 RETURN_VALUE
None
>>> print dis.dis(lambda: [])
1 0 BUILD_LIST 0
3 RETURN_VALUE
None
list
는 문자열을 목록 객체로 변환하는
function
이므로
[]
는 bat에서 목록을 만드는 데 사용됩니다.
이것을 시도하십시오 (더 의미가있을 수 있습니다).
x = "wham bam"
a = list(x)
>>> a
["w", "h", "a", "m", ...]
동안
y = ["wham bam"]
>>> y
["wham bam"]
당신이 그것에 넣은 것을 포함하는 실제 목록을 제공합니다.