Понимание кортежа Python для оптимизации производительности списка - PullRequest
0 голосов
/ 26 марта 2019

У меня есть кортеж, скажем, atup = (1,3,4,5,6,6,7,78,8), который генерируется динамически по списку кортежей при повторении (выход генератора).Каждый кортеж должен быть преобразован в список, чтобы каждый элемент кортежа мог быть преобразован далее и использован в методе.Делая это, я с удивлением узнал, что простое выполнение списка (atup) намного быстрее, чем использование такого понимания списка [i for i in atup].Вот что я сделал:

Тест производительности 1:

timeit.timeit('list((1,3,4,5,6,6,7,78,8))', number=100000)
0.02268475245609025

Тест производительности 2:

timeit.timeit('[i for i in (1,3,4,5,6,6,7,78,8)]', number=100000)
0.05304025196801376

Не могли бы вы объяснить это?

Ответы [ 2 ]

2 голосов
/ 26 марта 2019

Понимание списка должно перебирать кортеж на уровне Python:

>>> dis.dis("[i for i in (1,2,3)]")
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x1075c0c90, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_CONST               5 ((1, 2, 3))
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

list выполняет итерации по самому кортежу и использует C API для этого, не проходя (в значительной степени) модель данных Python.

>>> dis.dis("list((1,2,3))")
  1           0 LOAD_NAME                0 (list)
              2 LOAD_CONST               3 ((1, 2, 3))
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

Итерация уровня Python более отчетливо видна в Python 2, который реализует списки в другом виде.

>>> def f():
...   return [i for i in (1,2,3)]
...
>>> dis.dis(f)
  2           0 BUILD_LIST               0
              3 LOAD_CONST               4 ((1, 2, 3))
              6 GET_ITER
        >>    7 FOR_ITER                12 (to 22)
             10 STORE_FAST               0 (i)
             13 LOAD_FAST                0 (i)
             16 LIST_APPEND              2
             19 JUMP_ABSOLUTE            7
        >>   22 RETURN_VALUE

Как указывает @blhsing, вы можете разобрать объект кода, сгенерированный списком в Python 3, чтобы увидеть то же самое.

>>> code = compile('[i for i in (1,2,3)]', '', 'eval')
>>> dis(code.co_consts[0])
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (i)
              8 LOAD_FAST                1 (i)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
1 голос
/ 26 марта 2019

Конструктор list реализован исключительно на C и поэтому имеет минимальные накладные расходы, в то время как при понимании списка компилятор Python должен построить временную функцию, построить итератор, сохранить выходные данные итератора как переменную i и загрузить переменная i для добавления в список, который содержит намного больше байт-кодов Python, чем просто загрузка кортежа и вызов конструктора list.

...