Нарезка в Python 3.4 медленнее?

Этот вопрос и мой ответ заставили меня задуматься об этом своеобразном различии между Python 2.7 и Python 3.4. Возьмем простой пример кода:

import timeit
import dis

c = 1000000
r = range(c)
def slow():
    for pos in range(c):
        r[pos:pos+3]

dis.dis(slow)

time = timeit.Timer(lambda: slow()).timeit(number=1)
print('%3.3f' % time)

В Python 2.7 я последовательно получаю 0.165~, а для Python 3.4 я последовательно получаю 0.554~. Единственное существенное различие между дизассемблиями заключается в том, что Python 2.7 испускает SLICE+3 байтовый код, в то время как Python 3.4 испускает BUILD_SLICE, а затем BINARY_SUBSCR. Обратите внимание, что я удалил кандидатов на потенциальное замедление от другого вопроса, а именно, строки и тот факт, что xrange не существует в Python 3.4 (который должен быть похож на последний range class anyways).

Использование itertools' islice дает почти одинаковые тайминги между ними, поэтому я очень подозреваю, что это нарезка, что причина разницы здесь.

Почему это происходит и есть ли ссылка на авторитетный источник, документирующий изменение в поведении?

EDIT: В ответ на ответ я обернул объекты range в list, что дало заметное ускорение. Однако, когда я увеличил количество итераций в timeit, я заметил, что разницы во времени стали больше и больше. В качестве проверки на работоспособность я заменил срез на None, чтобы увидеть, что произойдет.

500 итераций в timeit.

c = 1000000
r = list(range(c))
def slow():
    for pos in r:
        None

дает 10.688 и 9.915 соответственно. Замена цикла for на for pos in islice(r, 0, c, 3) дает 7.626 и 6.270 соответственно. Заменив None на r[pos], получим 20~ и 28~ соответственно. r[pos:pos+3] дает 67.531 и 106.784 соответственно.

Как вы можете видеть, разница во времени огромна. Опять же, я все еще уверен, что проблема не связана напрямую с range.

Ответ 1

На Python 2.7 вы повторяете список и нарезаете список. На Python 3.4 вы выполняете итерацию над range и срезанием range.

Когда я запускаю тест со списком в обеих версиях Python:

from __future__ import print_function
import timeit
print(timeit.timeit('x[5:8]', setup='x = list(range(10))'))

Я получаю 0.243554830551 секунд на Python 2.7 и 0.29082867689430714 секунд на Python 3.4, гораздо меньшая разница.


Разница в производительности, которую вы видите после устранения объекта range, намного меньше. В первую очередь это связано с двумя факторами: добавление на Python 3 немного медленнее, а Python 3 нужно пройти через __getitem__ с помощью объекта среза для разрезания, тогда как Python 2 имеет __getslice__.

Я не смог воспроизвести разницу во времени, которую вы видели с помощью r[pos]; у вас может быть некоторый смешающий фактор в этом тесте.