Являются ли кортежи более эффективными, чем списки в Python?

StackOverflow https://stackoverflow.com/questions/68630

Вопрос

Есть ли какая-либо разница в производительности между кортежами и списками, когда дело доходит до создания экземпляра и извлечения элементов?

Это было полезно?

Решение

Тот Самый 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

Другие советы

В общем, вы могли бы ожидать, что кортежи будут немного быстрее.Однако вам обязательно следует протестировать ваш конкретный случай (если разница может повлиять на производительность вашей программы - помните, что "преждевременная оптимизация - корень всего зла").

Python делает это очень простым: время пришло это твой друг.

$ 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

Таким образом, в этом случае создание экземпляра происходит почти на порядок быстрее для кортежа, но доступ к элементу на самом деле несколько быстрее для списка!Поэтому, если вы создаете несколько кортежей и обращаетесь к ним много-много раз, возможно, на самом деле будет быстрее использовать вместо них списки.

Конечно, если ты хочешь изменение элемент, список определенно будет быстрее, поскольку вам нужно будет создать совершенно новый кортеж, чтобы изменить один его элемент (поскольку кортежи неизменяемы).

Краткие сведения

Кортежи, как правило, работают лучше, чем списки почти в каждой категории:

1) Кортежи могут быть постоянный сложенный.

2) Кортежи можно использовать повторно, а не копировать.

3) Кортежи компактны и не перегружаются.

4) Кортежи напрямую ссылаются на свои элементы.

Кортежи могут быть постоянно свернутыми

Кортежи констант могут быть предварительно вычислены оптимизатором peephole в Python или AST-optimizer.Списки, с другой стороны, создаются с нуля:

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

Вот комментарий от Объекты/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;

Обратите внимание, что объект tuple включает в себя два указателя данных напрямую, в то время как объект list имеет дополнительный уровень косвенного обращения к внешнему массиву, содержащему два указателя данных.

Кортежи, будучи неизменяемыми, более эффективно используют память;списки для повышения эффективности перераспределяют память, чтобы разрешить добавление без постоянной reallocs.Итак, если вы хотите выполнить итерацию по постоянной последовательности значений в вашем коде (например 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 очень эффективен при чтении, заключается в том, что он неизменяем.

Почему неизменяемые объекты легко читаются?

Причина в том, что кортежи могут храниться в кэше памяти, в отличие от списков.Программа всегда считывает данные из ячейки памяти списков, поскольку она изменчива (может измениться в любое время).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top