Các bộ dữ liệu có hiệu quả hơn các danh sách trong Python không?


225

Có sự khác biệt về hiệu năng giữa các bộ dữ liệu và danh sách khi nói đến việc khởi tạo và truy xuất các phần tử không?



2
Nếu bạn quan tâm đến bộ nhớ được sử dụng giữa các loại khác nhau, hãy xem cốt truyện này tôi đã thực hiện: stackoverflow.com/a/30008338/2087463
tmthydvnprt 13/03/2016

Câu trả lời:


172

Các dismô-đun disassembles mã byte cho một chức năng và rất hữu ích để thấy sự khác biệt giữa các bộ và danh sách.

Trong trường hợp này, bạn có thể thấy rằng việc truy cập một phần tử tạo mã giống hệt nhau, nhưng việc gán một bộ dữ liệu nhanh hơn nhiều so với việc gán danh sách.

>>> 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, chỉ là cùng một mã byte được tạo hoàn toàn không có nghĩa là các hoạt động tương tự xảy ra ở cấp độ C (và do đó là cpu). Hãy thử tạo một lớp ListLikevới một __getitem__thứ làm chậm một cách khủng khiếp, sau đó tháo rời x = ListLike((1, 2, 3, 4, 5)); y = x[2]. Mã byte sẽ giống như ví dụ tuple ở trên hơn ví dụ về danh sách, nhưng bạn có thực sự tin rằng điều đó có nghĩa là hiệu suất sẽ tương tự không?
mzz

2
Có vẻ như bạn đang nói rằng một số loại hiệu quả hơn những loại khác. Điều đó có ý nghĩa, nhưng chi phí chung của các thế hệ danh sách và tuple dường như trực giao với kiểu dữ liệu liên quan, với lời cảnh báo rằng chúng là danh sách và bộ dữ liệu cùng loại.
Đánh dấu Harrison

11
Số lượng mã byte, như số dòng mã, có ít mối quan hệ với tốc độ thực hiện (và do đó hiệu quả và hiệu suất).
martineau

18
Mặc dù gợi ý bạn có thể kết luận bất cứ điều gì từ việc đếm ops là sai, nhưng điều này cho thấy sự khác biệt chính: các bộ dữ liệu không đổi được lưu trữ như vậy trong mã byte và chỉ được tham chiếu khi sử dụng, trong khi các danh sách cần được xây dựng khi chạy.
poolie

6
Câu trả lời này cho chúng ta thấy Python thừa nhận các hằng số tuple. Đó là điều tốt để biết! Nhưng điều gì xảy ra khi cố gắng xây dựng một tuple hoặc một danh sách từ các giá trị biến?
Tom

211

Nói chung, bạn có thể mong đợi các bộ dữ liệu sẽ nhanh hơn một chút. Tuy nhiên, bạn chắc chắn nên kiểm tra trường hợp cụ thể của mình (nếu sự khác biệt có thể ảnh hưởng đến hiệu suất của chương trình của bạn - hãy nhớ "tối ưu hóa sớm là gốc rễ của mọi tội lỗi").

Python làm điều này rất dễ dàng: timeit là bạn của bạn.

$ 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

và ...

$ 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

Vì vậy, trong trường hợp này, khởi tạo gần như là một thứ tự cường độ nhanh hơn cho bộ dữ liệu, nhưng truy cập mục thực sự có phần nhanh hơn cho danh sách! Vì vậy, nếu bạn đang tạo một vài bộ dữ liệu và truy cập chúng nhiều lần, thực tế có thể nhanh hơn để sử dụng danh sách.

Tất nhiên, nếu bạn muốn thay đổi một mục, danh sách chắc chắn sẽ nhanh hơn vì bạn cần tạo một bộ hoàn toàn mới để thay đổi một mục của nó (vì các bộ dữ liệu là bất biến).


3
Phiên bản nào của python là thử nghiệm của bạn với!
Matt Joiner

2
Có một thử nghiệm thú vị - python -m timeit "x=tuple(xrange(999999))"vs python -m timeit "x=list(xrange(999999))". Như người ta có thể mong đợi, phải mất một chút thời gian để cụ thể hóa một tuple hơn một danh sách.
Hamish Grubijan

3
Có vẻ kỳ quái là truy cập tuple chậm hơn truy cập danh sách. Tuy nhiên, thử trong Python 2.7 trên PC Windows 7 của tôi, sự khác biệt chỉ là 10%, vì vậy không quan trọng.
ToolmakerSteve 15/12/13

51
FWIW, truy cập danh sách nhanh hơn truy cập trong Python 2 nhưng chỉ vì có một trường hợp đặc biệt cho các danh sách trong BINARY_SUBSCR trong Python / ceval.c. Trong Python 3, việc tối ưu hóa đó không còn nữa và việc truy cập bộ dữ liệu trở nên nhanh hơn một chút so với truy cập danh sách.
Raymond Hettinger

3
@yoopoo, thử nghiệm đầu tiên tạo ra một danh sách một triệu lần, nhưng lần thứ hai tạo ra một danh sách một lần và truy cập nó một triệu lần. Các -s "SETUP_CODE"được thực hiện trước mã thời gian thực tế.
leewz

203

Tóm lược

Tuples có xu hướng hoạt động tốt hơn danh sách trong hầu hết mọi danh mục:

1) Tuples có thể liên tục gấp .

2) Tuples có thể được sử dụng lại thay vì sao chép.

3) Tuples nhỏ gọn và không phân bổ quá mức.

4) Tuples trực tiếp tham chiếu các yếu tố của họ.

Tuples có thể được gấp lại liên tục

Các bộ hằng số có thể được tính toán trước bằng trình tối ưu hóa lổ nhìn trộm của Python hoặc trình tối ưu hóa AST. Danh sách, mặt khác, được xây dựng từ đầu:

    >>> 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 

Các bộ dữ liệu không cần phải sao chép

Chạy tuple(some_tuple)trở lại ngay lập tức chính nó. Vì các bộ dữ liệu là bất biến, nên chúng không phải sao chép:

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

Ngược lại, list(some_list)yêu cầu tất cả dữ liệu được sao chép vào một danh sách mới:

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

Tuples không phân bổ quá mức

Vì kích thước của một tuple là cố định, nó có thể được lưu trữ gọn hơn so với các danh sách cần phân bổ quá mức để làm cho hoạt động chắp thêm () hiệu quả.

Điều này mang lại cho tuples một lợi thế không gian đẹp:

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

Dưới đây là nhận xét từ Đối tượng / listobject.c giải thích những gì danh sách đang làm:

/* 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.
 */

Tuples đề cập trực tiếp đến các yếu tố của họ

Tham chiếu đến các đối tượng được kết hợp trực tiếp trong một đối tượng tuple. Ngược lại, các danh sách có thêm một lớp gián tiếp với một mảng con trỏ bên ngoài.

Điều này mang lại cho tuples một lợi thế tốc độ nhỏ để tra cứu được lập chỉ mục và giải nén:

$ 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

Đây là cách (10, 20)lưu trữ bộ dữ liệu:

    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;

Đây là cách danh sách [10, 20]được lưu trữ:

    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;

Lưu ý rằng đối tượng tuple kết hợp trực tiếp hai con trỏ dữ liệu trong khi đối tượng danh sách có thêm một lớp cảm ứng cho một mảng bên ngoài giữ hai con trỏ dữ liệu.


19
Cuối cùng, ai đó đặt các cấu trúc C!
osman

1
Internally, tuples are stored a little more efficiently than lists, and also tuples can be accessed slightly faster. Làm thế nào bạn có thể giải thích kết quả từ câu trả lời của dF.
DRz

5
Khi làm việc với ~ 50k danh sách ~ 100 danh sách thành phần, việc di chuyển cấu trúc này sang bộ dữ liệu đã giảm thời gian tra cứu theo nhiều bậc độ lớn cho nhiều lần tra cứu. Tôi tin rằng điều này là do địa phương bộ đệm lớn hơn của bộ dữ liệu một khi bạn bắt đầu sử dụng bộ dữ liệu do loại bỏ lớp thứ hai của sự gián tiếp mà bạn chứng minh.
horta

tuple(some_tuple)chỉ trả về some_tuplechính nó nếu some_tuplelà băm có thể băm khi nội dung của nó được đệ quy bất biến và có thể băm. Nếu không, tuple(some_tuple)trả về một tuple mới. Ví dụ, khi some_tuplechứa các mặt hàng có thể thay đổi.
Luciano Ramalho

Các tuple không phải lúc nào cũng nhanh hơn .Consider `` `t = () cho i trong phạm vi (1.100): t + = il = [] cho i trong phạm vi (1.1000): a.append (i)` `` Cái thứ hai nhanh hơn
melvil james

32

Tuples, là bất biến, là bộ nhớ hiệu quả hơn; danh sách, để hiệu quả, tổng thể bộ nhớ để cho phép nối thêm mà không cần reallocs. Vì vậy, nếu bạn muốn lặp qua một chuỗi các giá trị không đổi trong mã của mình (ví dụ:for direction in 'up', 'right', 'down', 'left': ), các bộ dữ liệu được ưu tiên, vì các bộ dữ liệu đó được tính toán trước trong thời gian biên dịch.

Tốc độ truy cập phải giống nhau (cả hai đều được lưu dưới dạng các mảng liền kề trong bộ nhớ).

Nhưng, alist.append(item)được ưu tiên nhiều atuple+= (item,)khi bạn xử lý dữ liệu có thể thay đổi. Hãy nhớ rằng, bộ dữ liệu được dự định sẽ được coi là bản ghi mà không có tên trường.


1
thời gian biên dịch trong python là gì?
balki

1
@balki: thời gian khi nguồn python được biên dịch thành mã byte (mã byte có thể được lưu dưới dạng tệp .py [co]).
tzot

Một trích dẫn sẽ là tuyệt vời nếu có thể.
Grijesh Chauhan

9

Bạn cũng nên xem xét arraymô-đun trong thư viện chuẩn nếu tất cả các mục trong danh sách hoặc bộ dữ liệu của bạn có cùng loại C. Nó sẽ mất ít bộ nhớ hơn và có thể nhanh hơn.


15
Nó sẽ chiếm ít bộ nhớ hơn, nhưng thời gian truy cập có thể sẽ chậm hơn một chút, thay vì nhanh hơn. Truy cập một phần tử yêu cầu giá trị đóng gói phải được bỏ hộp đến một số nguyên thực, điều này sẽ làm chậm quá trình.
Brian

5

Đây là một tiêu chuẩn nhỏ khác, chỉ vì lợi ích của nó ..

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)

Hãy trung bình những điều này:

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

Bạn có thể gọi nó gần như không kết luận.

Nhưng chắc chắn, tuples mất 101.239%thời gian, hoặc 1.239%thêm thời gian để thực hiện công việc so với danh sách.


4

Tuples nên hiệu quả hơn một chút và vì điều đó, nhanh hơn danh sách vì chúng là bất biến.


4
Tại sao bạn nói rằng sự bất biến, trong và của chính nó, làm tăng hiệu quả? Đặc biệt là hiệu quả của việc khởi tạo và truy xuất?
Blair Conrad

1
Có vẻ như câu trả lời của Mark ở trên của tôi đã trình bày các hướng dẫn tháo rời về những gì xảy ra bên trong Python. Bạn có thể thấy rằng việc khởi tạo cần ít hướng dẫn hơn, tuy nhiên trong trường hợp này, việc truy xuất rõ ràng là giống hệt nhau.
ctcherry

các bộ dữ liệu bất biến có quyền truy cập nhanh hơn các danh sách có thể thay đổi
noobninja

-6

Lý do chính khiến Tuple rất hiệu quả trong việc đọc là vì nó không thay đổi.

Tại sao các đối tượng bất biến dễ đọc?

Lý do là các bộ dữ liệu có thể được lưu trữ trong bộ nhớ cache, không giống như danh sách. Chương trình luôn đọc từ vị trí bộ nhớ danh sách vì nó có thể thay đổi (có thể thay đổi bất cứ lúc nào).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.