Рубрики
Без рубрики

Xrange python xrange vs. ряд vs. ряд

Простой вопрос, пока я преподавал классу о итераторах и генераторах, студента … Теги с Python, внутренними, программированием, c.

Хотя я преподавал классу о итераторах и генераторах, студент задал мне вопрос, который не казался таким интересным. «Что быстрее – xrange или ряд?». Я пытался объяснить ему, что во многих случаях лучше использовать xrange вместо диапазона – но из-за использования более низкого использования памяти, а не из-за времени выполнения. Он продолжил: «Да, я понимаю. Но какой из них быстрее? “. Ну, довольно очевидно, что наращивание списка с диапазоном требует больше времени, чем вызывая xrange – поскольку при вызове xrange нет реального списка. Итак, чтобы фразу более точно, вопрос в том, что быстрее – звонить в следующем итераторе над объектом списка, или вызов рядом итератора объекта xrange?

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

  • Получение следующего объекта в списке в основном обращается к массиву в определенном конкретном индексе (и увеличивая этот индекс)
  • Получение следующего объекта объекта XRANGE в основном увеличивается некоторый номер и возвращает его. Опять же, очень простая операция.

Так что быстрее? Прямой доступ к памяти или простое дополнение? Я думал, что, возможно, добавление (xrange) было бы немного быстрее, но не очень много.

Что должна говорить документацию о xrange? Ну, согласно Docstring Xrange, он немного быстрее, чем диапазон:

In [1]: xrange?
Docstring:
xrange(stop) -> xrange object
xrange(start, stop[, step]) -> xrange object


Like range(), but instead of returning a list, returns an object that
generates the numbers in the range on demand.  For looping, this is
slightly faster than range() and more memory efficient.

Но Docstring не говорит точно, что быстрее о диапазоне. Таким образом, мы должны догадаться, что они обращаются к выводу на следующий метод (итерацией по ресекту)

Но, «формальная» документация Python, не говорит ничего ясно о скорости xrange:

xrange (Стоп) xrange (запуск, Стоп [, шаг]) Эта функция очень похожа на диапазон (), но возвращает объект xrange вместо списка. Это непрозрачный тип последовательности, который дает одинаковые значения, что и соответствующий список, не сохраняя их все одновременно. Преимущество XRANGE () в пределах диапазона () минимальна (так как xrange () все еще должно создавать значения, когда их задают), за исключением случаев, когда на память-голодной машине используется очень большой диапазон или когда все элементы диапазона Никогда не использовал (например, когда цикл обычно прекращается с перерывом). Для получения дополнительной информации о объектах XRANGE см. Тип XRANGE и типов последовательности – STR, Unicode, Список, кортеж, ByTeArRay, Buffer, Xrange.

Вы можете увидеть эту документацию здесь – https://docs.cython.org/2/Library/functions.html#xrange.

Одна из многих вещей, которые мне нравятся в Python, так это то, что это действительно легко попробовать вещи. Если вы хотите знать, что быстрее, просто запустите код, и посмотрите, что быстрее! Мы хотим устранить время, наращивающее список с диапазоном и фокусироваться только на самой итерации. IPython имеет очень хорошую функцию для проверки времени выполнения методов -% TimeIt. Мы можем использовать его с рядом с списком ITERATOR/XRANGE ITERATOR и посмотрим, какой из них быстрее. Мы будем использовать список/xrange размером 10 000 000 для теста. Нам также потребуется ограничить количество тестов, которые выполняют% timeit, поэтому мы не будем использовать исключение задержания (конец списка/xrange). Вот результаты:

In [4]: list_iterator = iter(range(10 ** 7))

In [5]: %timeit -n 1000000 list_iterator.next()
1000000 loops, best of 3: 68.7 ns per loop

In [6]: xrange_iterator = iter(xrange(10 ** 7))

In [7]: %timeit -n 1000000 xrange_iterator.next()
1000000 loops, best of 3: 65.9 ns per loop

Кажется немного быстрее, верно? Примерно на 5%, но результаты не так согласованы:

In [8]: xrange_iterator = iter(xrange(10 ** 7))

In [9]: %timeit -n 1000000 xrange_iterator.next()
1000000 loops, best of 3: 71.6 ns per loop

Иногда xrange медленнее! Источником различий, вероятно, моя беговая среда (довольно старый Ubuntu 14.04 VM, с 1 ГБ памяти и один CPU). Важная часть состоит в том, что она не похоже, что между скоростью Xrange есть большой разные между скоростью xrange и варьируется в Python 2.7.

Ну, в Python 3 рентгеновства были удалены полностью с языка. Теперь объекты диапазона ведут себя как старые и любимые реснички, и они возвращают объект, который вы можете считать, но который не является списком. Таким образом, вы подумаете, что новые объекты диапазона будут быстрее, чем старые объекты Xrange, верно? Давайте проверим это. Помните, что. Text был также удален с Python 3, и теперь нам нужно позвонить следующему (iTerator) вместо iTerator.Next ().

In [1]: range_iterator = iter(range(10 ** 7))

In [2]: %timeit -n 1000000 next(range_iterator)
111 ns ± 7.63 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Это выше 110 нс для операции, вместо ~ 70 нс в Python 2! И как с Python 2, кажется, что итализация списков занимает подобное время:

In [3]: list_iterator = iter(list(range(10 ** 7)))

In [4]: %timeit -n 1000000 next(list_iterator)
95.7 ns ± 7.13 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Это даже немного быстрее Затем Xrange, но все же – это на 50% медленнее, чем итерация в Python 2.

Это на самом деле известное явление с Python 3. Вы можете прочитать возможное объяснение для этого здесь – https://stackoverflow.com/questions/23453133/is-there-a-reason-python-3-enumerate-slower-than-python-2 (Однако, на мой взгляд, этот ответ не кажется правильным – так как мы используем только небольшие числа – ниже Sys.maxint)

Ответ в приведенной выше переполненной ссылке стека заставил меня удивляться – что произойдет с очень высокими числами? Будет варьироваться и список (диапазон) все еще есть подобные скорости? Моя интуиция сказала, что результаты будут похожи на результаты, которые мы видели раньше – около 100 нс для каждого вызова к другому. Но опять же – лучший способ найти вещи – попробовать! Итак, давайте проверим это:

In [2]: list_iterator_high_numbers = iter(list(range(2 ** 64, 2 ** 64 + 10 ** 7)))

In [3]: %timeit -n 1000000 next(list_iterator_high_numbers)
97 ns ± 12.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Как видно, результат для списков очень похож на результаты ранее. Нет удивления здесь.

In [4]: range_iterator_high_numbers = iter(range(2 ** 64, 2 ** 64 + 10 ** 7))

In [5]: %timeit -n 1000000 next(range_iterator_high_numbers)
204 ns ± 9.15 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Теперь это сюрприз! Итализация на высоких числах с диапазоном есть Многое медленнее, чем итерация по низким числам. Вызов Далее принимает в среднем на 100% больше времени!

Почему это происходит? Я не мог найти намек в документацию, поэтому я должен был сделать следующий шаг и

Еще одна великая вещь о Python заключается в том, что вы можете просто прочитать его исходный код. Если вы хотите понять что-то более тщательно, или если вы немного любопытно, вы можете прочитать сам код! Соответствующий модуль, который мы хотим посмотреть, – это объекты/Roundobject.c, который содержит реализацию объектов диапазона в Python 3. Вы можете найти это здесь – https://github.com/python/cpython/blob/master/objects/rangeObject.c. Довольно легко найти код «Следующий» метод, который является методом, который мы заинтересованы в:

Код довольно прямой. Если диапазон еще не достиг своего конца, увеличьте индекс за другим, умножите его на «Step», добавьте к «Пуск» и верните значение в качестве длинного объекта Python. (Проблема литья не имеет отношения к нам сейчас). Похоже, эффективный код. Почему это менее эффективно для больших чисел? Но подождите секунду. Это не работает на цифры, которые «дольше», чем длинные вообще! Мы только что увидели, что диапазон поддерживает очень высокие числа, с более чем 64 битами. Длинные переменные в C могут хранить только номера до 32 (или 64) битов. Должен быть какой-то другой код, который обрабатывает более высокие числа. Ну, в моем первом взгляде я пропустил комментарий чуть выше этого метода:

Это ясно говорит, что есть одна реализация для C Longs (тот, который мы только что увидели), а другая реализация для Python Ints. Этот метод появляется ~ 250 строк позже:

Посмотри на весь этот код. Вместо 3-4 основных арифметических операций, которые мы увидели в Anditeiter_next, здесь в Longrangeiter_next есть гораздо более сложные операции. Вы не можете просто использовать «+», чтобы добавить Python INTS – вам нужно использовать Pynumber_add. Вы не можете использовать «*», чтобы умножить «индекс» и «шаг» – вам необходимо использовать Pynumbome_Multiple. Неудивительно, что призвание дальше занимает больше времени для «длинных длинных» чисел – гораздо больше вещей происходит под капотом. Но для следующего метода обычных списков, не имеет значения, какие значения внутри списка – вам не нужно делать какие-либо «сложные» арифметические операции, такие как умножение Python INT, чтобы получить элемент из списка.

Итак, что мы видели здесь? Мы начали с простого вопроса – что быстрее, xrange или ряд? И мы видели, что ответ не так очевидно. Мы продолжили, сравнивая скорость Python 2 и Python 3, и мы были удивлены, чтобы увидеть, что Python 3 немного медленнее в этой области. Затем мы смотрели на диапазоны с большими числами и видели, что они намного медленнее, чем «ниже» диапазоны. Нам пришлось исследовать исходный код Python, чтобы узнать причину этого.

Это все для этого поста, я надеюсь, что вам понравилось.

Оригинал: “https://dev.to/guy_gold/xrange-vs-range-vs-range-kd4”