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_FUNCTIONlist() 대한 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 은 함수를 즉시 호출하는 간단한 사람이 아닙니다. 대신 스택에서 객체를 가져오고 스택의 모든 인수를 가져온 다음 객체 유형에 따라 전환합니다. 그건:

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_calllist.__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"]

당신이 그것에 넣은 것을 포함하는 실제 목록을 제공합니다.





literals