최근의 처리 속도 비교 []
와 list()
그 발견 놀랐다 []
실행 빠르게 세 번 것보다 더 이상 list()
. 나는과 동일한 테스트를 실행 {}
하고 dict()
: 결과는 실질적으로 동일했다 []
및 {}
동안 모두 약 0.128sec / 만회했다 list()
및 dict()
약 0.428sec / 만회 각했다.
왜 이런거야? 수행 []
하고 {}
(아마 ()
와 ''
도) 자신의 명시 적 이름의 대응이 (동안 즉시 일부 빈 재고 리터럴의 복사본을 다시 전달 list()
, dict()
, tuple()
, str()
)가 완전히 실제로 요소가 있는지 여부, 개체를 만드는 방법에 대해 이동?
이 두 가지 방법이 어떻게 다른지 잘 모르겠지만 알고 싶습니다. 문서 나 SO에서 답을 찾을 수 없었으며 빈 괄호를 검색하는 것이 예상보다 문제가 많은 것으로 나타났습니다.
나는 호출하여 내 타이밍 결과를 가지고 timeit.timeit("[]")
와 timeit.timeit("list()")
, 및 timeit.timeit("{}")
및 timeit.timeit("dict()")
각각 목록 및 사전을 비교. Python 2.7.9를 실행 중입니다.
내가 최근에 발견 된 ” 왜 인 경우는 true보다 느린 경우 하나? “의 성능을 비교 if True
하는 if 1
과에 터치 보인다 비슷한 문자 – 대 – 글로벌 시나리오; 아마도 고려해 볼 가치가 있습니다.
답변
때문에 []
하고 {}
있습니다 리터럴 구문 . 파이썬은리스트 나 딕셔너리 객체를 만들기 위해 바이트 코드를 만들 수 있습니다 :
>>> import dis
>>> dis.dis(compile('[]', '', 'eval'))
1 0 BUILD_LIST 0
3 RETURN_VALUE
>>> dis.dis(compile('{}', '', 'eval'))
1 0 BUILD_MAP 0
3 RETURN_VALUE
list()
그리고 dict()
별도의 개체입니다. 이름을 확인하고 인수를 푸시하기 위해 스택을 포함해야하며 나중에 검색하기 위해 프레임을 저장해야하며 호출해야합니다. 모든 시간이 더 걸립니다.
빈 경우에는 최소한 현재 프레임을 유지 해야하는 LOAD_NAME
(글로벌 네임 스페이스와 __builtin__
모듈 을 검색해야 함 ) 뒤에 a CALL_FUNCTION
가 있어야합니다.
>>> dis.dis(compile('list()', '', 'eval'))
1 0 LOAD_NAME 0 (list)
3 CALL_FUNCTION 0
6 RETURN_VALUE
>>> dis.dis(compile('dict()', '', 'eval'))
1 0 LOAD_NAME 0 (dict)
3 CALL_FUNCTION 0
6 RETURN_VALUE
다음을 사용하여 이름 조회 시간을 별도로 지정할 수 있습니다 timeit
.
>>> import timeit
>>> timeit.timeit('list', number=10**7)
0.30749011039733887
>>> timeit.timeit('dict', number=10**7)
0.4215109348297119
시간 불일치에는 사전 해시 충돌이있을 수 있습니다. 해당 객체를 호출하는 시간에서 해당 시간을 빼고 리터럴을 사용하는 시간과 결과를 비교하십시오.
>>> timeit.timeit('[]', number=10**7)
0.30478692054748535
>>> timeit.timeit('{}', number=10**7)
0.31482696533203125
>>> timeit.timeit('list()', number=10**7)
0.9991960525512695
>>> timeit.timeit('dict()', number=10**7)
1.0200958251953125
따라서 개체를 호출하는 데는 1.00 - 0.31 - 0.30 == 0.39
천만 호출마다 추가 초가 걸립니다 .
전역 이름의 이름을 로컬로 지정하여 전역 조회 비용을 피할 수 있습니다 ( timeit
설정을 사용 하면 이름에 바인딩하는 모든 것이 로컬 임).
>>> timeit.timeit('_list', '_list = list', number=10**7)
0.1866450309753418
>>> timeit.timeit('_dict', '_dict = dict', number=10**7)
0.19016098976135254
>>> timeit.timeit('_list()', '_list = list', number=10**7)
0.841480016708374
>>> timeit.timeit('_dict()', '_dict = dict', number=10**7)
0.7233691215515137
그러나 그 CALL_FUNCTION
비용 을 결코 극복 할 수는 없습니다 .
답변
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
하는 함수 이기 때문에 []
방망이에서 목록을 만드는 데 사용됩니다. 이것을 시도하십시오 (더 의미가있을 수 있습니다).
x = "wham bam"
a = list(x)
>>> a
["w", "h", "a", "m", ...]
동안
y = ["wham bam"]
>>> y
["wham bam"]
당신이 그것에 넣은 것을 포함하는 실제 목록을 제공합니다.
답변
이 질문에 대한 대답은 훌륭하고이 질문을 완전히 다루고 있습니다. 관심있는 사람들을 위해 바이트 코드에서 한 단계 더 나아가겠습니다. CPython의 최신 저장소를 사용하고 있습니다. 이전 버전은 이와 관련하여 비슷하게 작동하지만 약간의 변경이있을 수 있습니다.
다음은 BUILD_LIST
for []
및 CALL_FUNCTION
for 각각에 대한 실행에 대한 분석 list()
입니다.
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
.
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
PyMethodType
? 아니요, 이전을 참조하십시오.PyFunctionType
? 노피, 이전 참조.
우리는 list
타입을 호출합니다 . 전달 된 인수 call_function
는 PyList_Type
입니다. CPython은 이제 일반 함수를 호출하여 _PyObject_FastCallKeywords
더 많은 함수 호출 이라고하는 호출 가능한 오브젝트를 처리해야 합니다.
이 함수는 특정 함수 유형 (이유를 이해할 수 없음)을 다시 확인한 다음 필요한 경우 kwargs에 대한 dict를 작성한 후 계속 호출 _PyObject_FastCallDict
합니다.
_PyObject_FastCallDict
마침내 우리를 어딘가에 데려옵니다! 수행 한 후 더 많은 검사를 이 잡고 tp_call
로부터 슬롯type
의 type
우리입니다, 그것을 잡고, 전달했습니다를 type.tp_call
. 그런 다음 전달 된 인수에서 튜플을 만들고 _PyStack_AsTuple
마지막 으로 호출을 할 수 있습니다 !
tp_call
일치 type.__call__
하는 목록 객체를 가져 와서 최종적으로 목록 객체를 만듭니다. 이 목록 호출 __new__
에 해당 PyType_GenericNew
그것을과 할당 메모리 PyType_GenericAlloc
: 이것은 실제로 함께 잡는 부분입니다 PyList_New
마지막으로 . 이전의 모든 것은 일반적인 방식으로 객체를 처리하는 데 필요합니다.
결국, type_call
전화list.__init__
사용 가능한 인수로 목록을 하고 초기화 한 다음, 우리가 왔던 방식으로 되돌아갑니다. 🙂
마지막으로을 기억 LOAD_NAME
하십시오. 여기에 기여하는 또 다른 사람입니다.
입력을 다룰 때 파이썬은 일반적으로 C
작업을 수행하기에 적합한 기능을 실제로 찾기 위해 농구대를 뛰어 넘어야한다는 것을 쉽게 알 수 있습니다. 그것은 그것의 동적, 사람이 마스크 수 있기 때문에 즉시 호출의 curtesy이없는 list
( 그리고 소년은 많은 사람들이 할 할 ) 다른 경로를주의해야합니다.
곳은 list()
많이 상실 다음 탐험 파이썬은 무엇을해야하는지 도대체 알아 할 필요가있다.
반면에 리터럴 구문은 정확히 한 가지를 의미합니다. 변경 될 수 없으며 항상 미리 결정된 방식으로 작동합니다.
각주 : 모든 기능 이름은 릴리스마다 변경 될 수 있습니다. 요점은 여전히 스탠드되며 향후 버전에서는 대부분 가능성이 높습니다.
답변
[]
보다 빠른 이유는 무엇list()
입니까?
가장 큰 이유는 파이썬 list()
이 사용자 정의 함수처럼 취급 한다는 것입니다.list
자신의 서브 클래 싱 된 목록을 사용하거나 deque와 같이 다른 것을 .
로 내장 목록의 새 인스턴스를 즉시 만듭니다 []
.
나의 설명은 당신에게 이것에 대한 직감을 제공하려고합니다.
설명
[]
일반적으로 리터럴 구문이라고합니다.
문법에서는이를 “목록 표시”라고합니다. 문서에서 :
목록 표시는 대괄호로 묶인 빈 일련의 표현식입니다.
list_display ::= "[" [starred_list | comprehension] "]"
목록 표시는 새 목록 객체를 생성하며, 내용은 표현식 목록 또는 이해로 지정됩니다. 쉼표로 구분 된 표현식 목록이 제공되면 해당 요소가 왼쪽에서 오른쪽으로 평가되어 순서대로 목록 오브젝트에 배치됩니다. 이해가 제공 될 때,리스트는 이해의 결과로 구성되는 요소들로 구성됩니다.
요컨대 이것은 유형의 내장 객체를 의미합니다. list
가 작성 .
이것을 피할 수는 없습니다. 즉, 파이썬은 최대한 빨리 할 수 있습니다.
반면 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
>>> 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
들어 []
파이썬 바이트 코드 수준에서 함수 호출이 없다 :
>>> dis.dis('[]')
1 0 BUILD_LIST 0
2 RETURN_VALUE
바이트 코드 수준에서 조회하거나 호출하지 않고 목록을 작성하는 것입니다.
결론
우리는 그 입증 list
범위 지정 규칙을 사용하여 사용자 코드를 가로 챌 수 있습니다, 그 list()
다음 호출에 대한 외모와 그것을 호출합니다.
반면 []
목록 표시 또는 리터럴은 이름 조회 및 함수 호출을 피합니다.