Чи є різниця між кортежами та списками, коли мова йде про інстанціювання та пошук елементів?
Чи є різниця між кортежами та списками, коли мова йде про інстанціювання та пошук елементів?
Відповіді:
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
ListLike
з а, __getitem__
який робить щось жахливо повільно, а потім розберіть x = ListLike((1, 2, 3, 4, 5)); y = x[2]
. Байт-код буде більше схожий на приклад кортежу вище, ніж приклад списку, але чи дійсно ви вірите, що означає, що продуктивність буде подібною?
Взагалі, ви можете очікувати, що кортежі будуть трохи швидшими. Однак ви обов'язково повинні перевірити свій конкретний випадок (якщо різниця може вплинути на ефективність вашої програми - пам’ятайте, що "передчасна оптимізація є коренем усього зла").
Python робить це дуже просто: 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
Так що в цьому випадку інстанція - це майже на порядок швидше для кортежу, але доступ до пунктів фактично дещо швидший для списку! Отже, якщо ви створюєте кілька кортежів і отримуєте доступ до них багато разів, можливо, натомість швидше використовувати списки.
Звичайно, якщо ви хочете змінити елемент, список, безумовно, буде швидшим, оскільки вам потрібно буде створити цілий новий кортеж, щоб змінити один елемент (оскільки кортежі незмінні).
python -m timeit "x=tuple(xrange(999999))"
проти python -m timeit "x=list(xrange(999999))"
. Як можна було б очікувати, на матеріалізацію кортежу, ніж списку, потрібно трохи більше часу.
-s "SETUP_CODE"
Виконуються до фактичного таймерного коду.
Кортежі, як правило, краще, ніж списки майже в кожній категорії:
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
Оскільки розмір кортежу фіксований, його можна зберігати більш компактно, ніж списки, яким потрібно перерозподілити, щоб зробити операції додавання () ефективними.
Це дає кортежам хорошу космічну перевагу:
>>> 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;
Зауважте, що об'єкт кортежу містить два вказівника даних безпосередньо, тоді як об'єкт списку має додатковий шар непрямості до зовнішнього масиву, що містить два покажчики даних.
Internally, tuples are stored a little more efficiently than lists, and also tuples can be accessed slightly faster.
Як ви могли б пояснити результати відповіді df. Тоді?
tuple(some_tuple)
повертається лише в some_tuple
тому випадку, коли він some_tuple
є хешируемим - коли його вміст є рекурсивно незмінним і хешируемим. В іншому випадку tuple(some_tuple)
повертається новий кортеж. Наприклад, коли some_tuple
містяться елементи, що змінюються.
Кортежі, будучи непорушними, ефективніше пам’яті; списки, для ефективності перерозподіляють пам'ять, щоб дозволити додавання без постійних realloc
s. Отже, якщо ви хочете повторити через постійну послідовність значень у вашому коді (наприклад for direction in 'up', 'right', 'down', 'left':
), кращі кортежі є переважними, оскільки такі кортежі попередньо розраховуються за час компіляції.
Швидкість доступу повинна бути однаковою (вони обидва зберігаються як суміжні масиви в пам'яті).
Але, alist.append(item)
це набагато більше переваги, atuple+= (item,)
коли ви маєте справу з змінними даними. Пам'ятайте, що кортежі призначені для трактування як записи без назви полів.
Ви також повинні врахувати array
модуль у стандартній бібліотеці, якщо всі елементи у вашому списку або кортежі одного типу C. Це займе менше пам'яті і може бути швидше.
Ось ще один невеликий орієнтир, просто заради нього ..
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%
додаткового часу, щоб виконати роботу порівняно зі списками.
Кортежі повинні бути трохи ефективнішими, а тому швидшими, ніж списки, оскільки вони незмінні.
Основна причина, щоб Tuple був дуже ефективним у читанні, це тому, що він непорушний.
Причина в тому, що кортежі можуть зберігатися в кеш-пам'яті, на відміну від списків. Програма завжди читає зі списку пам'яті місця, оскільки воно є змінним (може змінюватися будь-коли).