튜플은 파이썬의 목록보다 더 효율적입니까?


답변:


172

dis모듈은 함수의 바이트 코드를 디스 어셈블하고 튜플과 목록의 차이점을 보는 데 유용합니다.

이 경우 요소에 액세스하면 동일한 코드가 생성되지만 튜플을 할당하는 것이 목록을 할당하는 것보다 훨씬 빠르다는 것을 알 수 있습니다.

>>> def a():
...     x=[1,2,3,4,5]
...     y=x[2]
...
>>> def b():
...     x=(1,2,3,4,5)
...     y=x[2]
...
>>> import dis
>>> dis.dis(a)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 LOAD_CONST               4 (4)
             12 LOAD_CONST               5 (5)
             15 BUILD_LIST               5
             18 STORE_FAST               0 (x)

  3          21 LOAD_FAST                0 (x)
             24 LOAD_CONST               2 (2)
             27 BINARY_SUBSCR
             28 STORE_FAST               1 (y)
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE
>>> dis.dis(b)
  2           0 LOAD_CONST               6 ((1, 2, 3, 4, 5))
              3 STORE_FAST               0 (x)

  3           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               2 (2)
             12 BINARY_SUBSCR
             13 STORE_FAST               1 (y)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

66
Err, 동일한 바이트 코드가 생성되었다고해서 동일한 작업이 C (따라서 CPU) 수준에서 발생한다는 것을 의미하지는 않습니다. 엄청나게 느린 일을 하는 클래스 ListLike를 만들어서 __getitem__분해하십시오 x = ListLike((1, 2, 3, 4, 5)); y = x[2]. 바이트 코드는 목록 예제보다 위의 튜플 예제와 비슷하지만 성능이 비슷하다는 것을 정말로 믿습니까?
mzz

2
어떤 유형은 다른 유형보다 효율적이라고 말합니다. 의미가 있지만 목록 및 튜플 생성의 오버 헤드는 관련된 데이터 유형과 직교하는 것으로 보입니다. 동일한 데이터 유형의 목록 및 튜플이라는 경고가 있습니다.
마크 해리슨

11
코드 라인 수와 같은 바이트 코드 수는 실행 속도 (효율 및 성능)와 거의 관계가 없습니다.
martineau

18
ops 계산에서 무엇이든 결론을 내릴 수 있다는 제안은 잘못 안내되었지만 중요한 차이점을 보여줍니다. 상수 튜플은 바이트 코드와 같이 저장되고 사용될 때 참조되지만 런타임에 목록을 작성해야합니다.
poolie

6
이 답변은 파이썬이 튜플 상수를 인정한다는 것을 보여줍니다. 알아두면 좋습니다! 그러나 변수 값에서 튜플 또는 목록을 만들려고 할 때 어떻게됩니까?
Tom

211

일반적으로 튜플이 약간 더 빠를 것으로 예상 할 수 있습니다. 그러나 특정 사례를 확실히 테스트해야합니다 (차이가 프로그램의 성능에 영향을 줄 수있는 경우 "미리 최적화가 모든 악의 근원"임을 명심하십시오).

파이썬은 이것을 매우 쉽게 만듭니다 : timeit 은 당신의 친구입니다.

$ python -m timeit "x=(1,2,3,4,5,6,7,8)"
10000000 loops, best of 3: 0.0388 usec per loop

$ python -m timeit "x=[1,2,3,4,5,6,7,8]"
1000000 loops, best of 3: 0.363 usec per loop

과...

$ python -m timeit -s "x=(1,2,3,4,5,6,7,8)" "y=x[3]"
10000000 loops, best of 3: 0.0938 usec per loop

$ python -m timeit -s "x=[1,2,3,4,5,6,7,8]" "y=x[3]"
10000000 loops, best of 3: 0.0649 usec per loop

따라서이 경우 인스턴스화는 튜플의 경우 거의 10 배 빠르지 만 실제로는 항목 액세스가 목록에서 다소 빠릅니다. 따라서 몇 개의 튜플을 만들고 여러 번 액세스하는 경우 실제로 목록을 사용하는 것이 더 빠를 수 있습니다.

당신이 원한다면 물론 변경 항목을 당신이 (튜플는 불변 때문에) 그것의 하나 개의 항목을 변경하는 완전히 새로운 튜플을 작성해야합니다 것 때문에, 목록은 확실히 빨라집니다.


3
어떤 버전의 파이썬을 테스트 했습니까!
매트 소목 장이

2
또 다른 흥미로운 테스트가있다 - python -m timeit "x=tuple(xrange(999999))"python -m timeit "x=list(xrange(999999))". 예상대로,리스트보다 튜플을 구체화하는 데 시간이 조금 더 걸립니다.
Hamish Grubijan

3
튜플 액세스가 목록 액세스보다 속도가 느린 기괴한 것 같습니다. 그러나 Windows 7 PC의 Python 2.7에서 시도하면 차이가 10 %에 불과하므로 중요하지 않습니다.
ToolmakerSteve

51
FWIW, Python 2에서 튜플 액세스보다 목록 액세스가 빠르지 만 Python / ceval.c의 BINARY_SUBSCR에 목록이있는 경우가 있기 때문에. 파이썬 3에서는 최적화가 사라지고 튜플 액세스가 목록 액세스보다 약간 빠릅니다.
Raymond Hettinger

3
@yoopoo, 첫 번째 테스트는 목록을 백만 번 작성하지만 두 번째 테스트는 목록을 한 번 작성하고 백만 번 액세스합니다. 은 -s "SETUP_CODE"실제 시간 제한 코드 전에 실행됩니다.
leewz

203

요약

튜플은 거의 모든 범주의 목록보다 성능이 우수합니다 .

1) 튜플은 일정하게 접을 수 있습니다 .

2) 튜플은 복사하는 대신 재사용 할 수 있습니다.

3) 튜플은 콤팩트하며 과도하게 할당되지 않습니다.

4) 튜플은 해당 요소를 직접 참조합니다.

튜플은 일정하게 접을 수 있습니다

튜플 상수는 Python의 틈새 최적화 도구 또는 AST 최적화 도구로 사전 계산할 수 있습니다. 반면에 목록은 처음부터 작성됩니다.

    >>> from dis import dis

    >>> dis(compile("(10, 'abc')", '', 'eval'))
      1           0 LOAD_CONST               2 ((10, 'abc'))
                  3 RETURN_VALUE   

    >>> dis(compile("[10, 'abc']", '', 'eval'))
      1           0 LOAD_CONST               0 (10)
                  3 LOAD_CONST               1 ('abc')
                  6 BUILD_LIST               2
                  9 RETURN_VALUE 

튜플은 복사 할 필요가 없습니다

달리기 tuple(some_tuple)는 즉시 그 자체로 돌아갑니다. 튜플은 변경할 수 없으므로 복사 할 필요가 없습니다.

>>> a = (10, 20, 30)
>>> b = tuple(a)
>>> a is b
True

반대로 list(some_list)모든 데이터를 새 목록으로 복사해야합니다.

>>> a = [10, 20, 30]
>>> b = list(a)
>>> a is b
False

튜플은 과도하게 할당되지 않습니다

튜플의 크기는 고정되어 있기 때문에 append () 연산을 효율적으로 수행 하기 위해 초과 할당해야하는 목록보다 더 컴팩트하게 저장할 수 있습니다 .

이것은 튜플에게 멋진 공간 이점을 제공합니다.

>>> import sys
>>> sys.getsizeof(tuple(iter(range(10))))
128
>>> sys.getsizeof(list(iter(range(10))))
200

Objects / listobject.c 의 주석은 다음과 같습니다 .

/* This over-allocates proportional to the list size, making room
 * for additional growth.  The over-allocation is mild, but is
 * enough to give linear-time amortized behavior over a long
 * sequence of appends() in the presence of a poorly-performing
 * system realloc().
 * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
 * Note: new_allocated won't overflow because the largest possible value
 *       is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
 */

튜플은 요소를 직접 참조합니다

객체에 대한 참조는 튜플 객체에 직접 통합됩니다. 대조적으로, 목록에는 외부 포인터 배열에 대한 추가 간접 계층이 있습니다.

이렇게하면 튜플에 색인 조회 및 포장 풀기에 작은 속도 이점이 있습니다.

$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'a[1]'
10000000 loops, best of 3: 0.0304 usec per loop
$ python3.6 -m timeit -s 'a = [10, 20, 30]' 'a[1]'
10000000 loops, best of 3: 0.0309 usec per loop

$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'x, y, z = a'
10000000 loops, best of 3: 0.0249 usec per loop
$ python3.6 -m timeit -s 'a = [10, 20, 30]' 'x, y, z = a'
10000000 loops, best of 3: 0.0251 usec per loop

튜플 (10, 20)을 저장 하는 방법은 다음과 같습니다 .

    typedef struct {
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
        Py_ssize_t ob_size;
        PyObject *ob_item[2];     /* store a pointer to 10 and a pointer to 20 */
    } PyTupleObject;

목록 [10, 20]이 저장되는 방법은 다음과 같습니다 .

    PyObject arr[2];              /* store a pointer to 10 and a pointer to 20 */

    typedef struct {
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
        Py_ssize_t ob_size;
        PyObject **ob_item = arr; /* store a pointer to the two-pointer array */
        Py_ssize_t allocated;
    } PyListObject;

튜플 객체는 두 개의 데이터 포인터를 직접 통합하는 반면 목록 객체는 두 개의 데이터 포인터를 보유하는 외부 배열에 대한 추가 간접 계층을 갖습니다.


19
마지막으로 누군가 C 구조체를 넣습니다!
osman

1
Internally, tuples are stored a little more efficiently than lists, and also tuples can be accessed slightly faster. 그렇다면 dF의 답변 결과를 어떻게 설명 할 수 있습니까?
DRz

5
~ 100k ~ ~ 100 개의 요소 목록으로 작업 할 때이 구조를 튜플로 이동하면 여러 번의 조회에서 조회 시간이 여러 배로 줄어 듭니다. 나는 이것이 당신이 설명하는 두 번째 간접 계층의 제거로 인해 튜플을 사용하기 시작하면 튜플의 캐시 로컬 성이 더 높기 때문이라고 생각합니다.
horta

tuple(some_tuple)컨텐츠가 재귀 적으로 변경 불가능하고 해시 가능한 some_tuple경우 some_tuple해시 가능 인 경우 에만 자체를 리턴 합니다 . 그렇지 않으면 tuple(some_tuple)새로운 튜플을 반환합니다. 예를 들어 some_tuple변경 가능한 항목이 포함 된 경우
Luciano Ramalho

튜플이 항상 더 빠른 것은 아닙니다. 범위 (1,100)에서 i의 경우```t = () : (1,1000) 범위에서 i의 경우 t + = il = [] : a.append (i)```두 번째 것은 더 빠름
melvil james

32

불변 인 튜플은 메모리 효율성이 더 높습니다. 효율을 높이기 위해 상수를 추가하지 않고 메모리를 전체적으로 나열하십시오 realloc. 따라서 코드에서 일정한 순서의 값을 반복하려면 (예 for direction in 'up', 'right', 'down', 'left'::) 튜플이 선호됩니다. 튜플은 컴파일 시간에 미리 계산되기 때문입니다.

액세스 속도는 같아야합니다 (모두 메모리에 연속 배열로 저장 됨).

그러나 가변 데이터를 처리 alist.append(item)atuple+= (item,)때보 다 선호됩니다 . 튜플은 필드 이름이없는 레코드로 취급되어야합니다.


1
파이썬에서 컴파일 시간은 무엇입니까?
balki

1
@balki : 파이썬 소스가 바이트 코드로 컴파일되는 시간 (바이트 코드는 .py [co] 파일로 저장 될 수 있음)
tzot

가능하면 인용이 좋을 것입니다.
Grijesh Chauhan

9

array목록 또는 튜플의 모든 항목이 동일한 C 유형 인 경우 표준 라이브러리 의 모듈 도 고려해야합니다 . 메모리가 적게 들고 더 빠를 수 있습니다.


15
메모리는 적게 들지만 액세스 시간은 더 빠르기보다는 조금 느려질 것입니다. 요소에 액세스하려면 압축 된 값을 실제 정수로 개봉해야 프로세스가 느려집니다.
Brian

5

여기에 다른 작은 벤치 마크가 있습니다.

In [11]: %timeit list(range(100))
749 ns ± 2.41 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [12]: %timeit tuple(range(100))
781 ns ± 3.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [1]: %timeit list(range(1_000))
13.5 µs ± 466 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [2]: %timeit tuple(range(1_000))
12.4 µs ± 182 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: %timeit list(range(10_000))
182 µs ± 810 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [8]: %timeit tuple(range(10_000))
188 µs ± 2.38 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [3]: %timeit list(range(1_00_000))
2.76 ms ± 30.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit tuple(range(1_00_000))
2.74 ms ± 31.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [10]: %timeit list(range(10_00_000))
28.1 ms ± 266 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit tuple(range(10_00_000))
28.5 ms ± 447 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

이것을 평균화하자.

In [3]: l = np.array([749 * 10 ** -9, 13.5 * 10 ** -6, 182 * 10 ** -6, 2.76 * 10 ** -3, 28.1 * 10 ** -3])

In [2]: t = np.array([781 * 10 ** -9, 12.4 * 10 ** -6, 188 * 10 ** -6, 2.74 * 10 ** -3, 28.5 * 10 ** -3])

In [11]: np.average(l)
Out[11]: 0.0062112498000000006

In [12]: np.average(t)
Out[12]: 0.0062882362

In [17]: np.average(t) / np.average(l)  * 100
Out[17]: 101.23946713590554

거의 결정적이지 않다고 부를 수 있습니다.

그러나 튜플은 목록에 비해 작업을 수행하는 데 시간 101.239%이 오래 걸리 거나 1.239%시간이 더 걸렸습니다 .


4

튜플은 불변이기 때문에 목록보다 약간 더 효율적이며 그 때문에 빠릅니다.


4
왜 불변성이 그 자체로 효율을 증가 시킨다고 말합니까? 특히 인스턴스화 및 검색 효율성?
블레어 콘래드

1
마크 위의 답변은 파이썬 내부에서 일어나는 일에 대한 분해 지침을 다룬 것 같습니다. 인스턴스화에 필요한 명령이 적다는 것을 알 수 있지만이 경우 검색은 분명히 동일합니다.
ctcherry

변경 불가능한 튜플은 변경 가능 목록보다 빠른 액세스를 제공합니다.
noobninja

-6

Tuple이 읽기에 매우 효율적인 주된 이유는 불변이기 때문입니다.

불변의 객체를 읽기 쉬운 이유는 무엇입니까?

리스트와 달리 튜플을 메모리 캐시에 저장할 수 있기 때문입니다. 프로그램은 변경 가능하므로 언제든지 목록 메모리 위치에서 읽습니다 (언제든지 변경할 수 있음).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.