Почему обратный (Mylist) так медленно? - PullRequest
8 голосов
/ 31 января 2020

( Обновление: Может происходить только в CPython 3.8 32-битных для Windows, поэтому не удивляйтесь, если вы не сможете воспроизвести его в других версиях. См. Таблицы в разделе Обновление.)

И iter, и reversed приводят к специализированным итераторам для списков:

>>> iter([1, 2, 3])
<list_iterator object at 0x031495C8>

>>> reversed([1, 2, 3])
<list_reverseiterator object at 0x03168310>

Но reversed намного медленнее:

> python -m timeit -s "a = list(range(1000))" "list(iter(a))"
50000 loops, best of 5: 5.76 usec per loop

> python -m timeit -s "a = list(range(1000))" "list(reversed(a))"
20000 loops, best of 5: 14.2 usec per loop

И я могу это последовательно воспроизвести. Позже я попробовал iter еще пять раз, взял 5,98, 5,84, 5,85, 5,87, 5,86. Затем reversed еще пять раз - 14,3, 14,4, 14,4, 14,5, 14,3.

Я подумал, что, возможно, iter выиграет от увеличения мест в памяти элементов списка, поэтому я попытался перевернуть список заранее. То же изображение:

> python -m timeit -s "a = list(range(1000)); a.reverse()" "list(iter(a))"
50000 loops, best of 5: 5.73 usec per loop

> python -m timeit -s "a = list(range(1000)); a.reverse()" "list(reversed(a))"
20000 loops, best of 5: 14.1 usec per loop

То же самое с sum:

> python -m timeit -s "a = list(range(1000))" "sum(iter(a))"
20000 loops, best of 5: 10.7 usec per loop

> python -m timeit -s "a = list(range(1000))" "sum(reversed(a))"
10000 loops, best of 5: 20.9 usec per loop

И с одинаковыми элементами:

> python -m timeit -s "a = [None] * 1000" "list(iter(a))"
50000 loops, best of 5: 6.35 usec per loop

> python -m timeit -s "a = [None] * 1000" "list(reversed(a))"
20000 loops, best of 5: 14.5 usec per loop

Почему обратный итератор намного медленнее?

Я использую CPython 3.8.1 32 бит на Windows 10 pro 64 бит версия 1903 с Intel i5-7200U (это HUAWEI MateBook X). Никакой специальной конфигурации, просто обычная Python установка при обычной Windows установке.

Обновление: Я провел более крупный автоматический тест с восемью различными Python версиями (все недавно установлены с настройками по умолчанию) на другом компьютере (Pentium N3700, Windows 10 Pro 64-bit, 1903). Используемое время c на л oop:

                32-bit              64-bit
CPython     iter   reversed     iter   reversed
 3.5.4      16.6     17.0       15.2     16.2
 3.6.8      16.8     17.2       14.9     15.8
 3.7.6      16.5     16.9       14.8     15.5
 3.8.1      16.3     22.1       14.6     15.5

Обратите внимание: две вещи:

  1. Python 3.8.1 32-битная reversed является единственной один намного медленнее. Можно объяснить, почему почти никто другой не мог воспроизвести его.
  2. Во всех семи других версиях reversed был немного медленнее, чем iter. Приблизительно 0,4 используют c в 32-разрядных и около 0,9 используют c в 64-разрядных.

Я выполнил эти 16 тестов в циклическом режиме в течение десяти раундов, и каждый раз, как показано выше является лучшим из его десяти источников времени. Каждое из 160 исходных значений времени было выполнено так:

python.exe -m timeit -r 5 -s "a = list(range(1000))" "list(iter(a))"
or
python.exe -m timeit -r 5 -s "a = list(range(1000))" "list(reversed(a))"

Время для каждого из 16 тестов было достаточно постоянным. Полная таблица (обратите внимание, что циклический перебор означает, что я запускал эти столбцы по столбцам, а не по строкам):

3.5.4 32-bit iter     [16.7, 16.6, 17.3, 16.6, 16.7, 16.6, 16.6, 16.6, 16.6, 16.7]
3.5.4 32-bit reversed [17.1, 17.1, 17.1, 17.2, 17.1, 17.1, 17.0, 17.1, 17.1, 17.1]
3.5.4 64-bit iter     [15.2, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.3, 15.4, 15.3]
3.5.4 64-bit reversed [16.8, 16.2, 16.3, 16.3, 16.2, 16.2, 16.2, 16.2, 16.2, 16.3]
3.6.8 32-bit iter     [17.3, 16.9, 16.8, 16.9, 16.9, 16.8, 16.9, 16.9, 16.8, 16.8]
3.6.8 32-bit reversed [17.2, 17.2, 17.2, 17.3, 17.3, 17.3, 17.3, 17.2, 17.2, 17.2]
3.6.8 64-bit iter     [15.0, 14.9, 15.9, 14.9, 14.9, 15.0, 14.9, 14.9, 14.9, 14.9]
3.6.8 64-bit reversed [15.8, 15.9, 16.4, 15.9, 15.9, 16.0, 15.8, 15.9, 15.9, 15.8]
3.7.6 32-bit iter     [16.6, 17.2, 16.6, 16.5, 16.7, 16.7, 16.5, 16.5, 16.5, 16.7]
3.7.6 32-bit reversed [17.2, 17.6, 17.0, 17.0, 16.9, 17.2, 17.3, 17.0, 17.5, 17.0]
3.7.6 64-bit iter     [14.8, 15.1, 14.9, 14.9, 14.8, 15.1, 14.9, 14.8, 15.0, 14.9]
3.7.6 64-bit reversed [16.0, 20.1, 15.7, 15.6, 15.6, 15.6, 15.7, 15.7, 15.8, 15.5]
3.8.1 32-bit iter     [16.4, 16.6, 16.3, 16.4, 16.5, 16.4, 16.5, 16.4, 16.8, 16.4]
3.8.1 32-bit reversed [22.3, 22.4, 22.2, 22.3, 22.3, 22.3, 22.5, 22.4, 22.3, 22.1]
3.8.1 64-bit iter     [14.6, 15.1, 14.6, 14.7, 14.7, 14.7, 14.7, 14.6, 14.6, 14.6]
3.8.1 64-bit reversed [15.5, 16.1, 15.5, 15.6, 15.5, 15.5, 15.5, 15.5, 15.5, 15.5]

Тот же тест в списке с миллионом значений (list(range(250)) * 4000). Времена mse c на л oop:

                32-bit              64-bit
CPython     iter   reversed     iter   reversed
 3.5.4      19.8     19.9       22.4     22.7
 3.6.8      19.8     19.9       22.3     22.6
 3.7.6      19.9     19.9       22.3     22.5
 3.8.1      19.8     24.9       22.4     22.6

Разница еще меньше, за исключением того, что reversed на 3.8.1 32-битная снова намного медленнее.

One больше, просто с CPython 3.8.0 вместо 3.8.1, где это также происходит.

                32-bit              64-bit
CPython     iter   reversed     iter   reversed
 3.5.4      19.5     19.6       21.9     22.2
 3.6.8      19.5     19.7       21.8     22.1
 3.7.6      19.5     19.6       21.7     22.0
 3.8.0      19.4     24.5       21.7     22.1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...