Подтвердить что ты не робот

Есть ли причина, по которой Python 3 перечисляет медленнее, чем Python 2?

Python 3, по-видимому, медленнее в перечислениях для минимального цикла, чем Python 2, с существенным запасом, который, похоже, ухудшается с более новыми версиями Python 3.

У меня есть Python 2.7.6, Python 3.3.3 и Python 3.4.0, установленные на моем 64-битном компьютере Windows (Intel i7-2700K - 3,5 ГГц) с 32-разрядной и 64-разрядной версиями каждого Установлен Python. Хотя нет существенной разницы в скорости выполнения между 32-битными и 64-битными для данной версии в пределах своих ограничений по доступу к памяти, существует очень значительная разница между различными уровнями версий. Я дам результаты времени говорить сами за себя:

C:\**Python34_64**\python -mtimeit -n 5 -r 2 -s"cnt = 0" "for i in range(10000000): cnt += 1"
5 loops, best of 2: **900 msec** per loop

C:\**Python33_64**\python -mtimeit -n 5 -r 2 -s"cnt = 0" "for i in range(10000000): cnt += 1"
5 loops, best of 2: **820 msec** per loop

C:\**Python27_64**\python -mtimeit -n 5 -r 2 -s"cnt = 0" "for i in range(10000000): cnt += 1"
5 loops, best of 2: **480 msec** per loop

Поскольку диапазон Python 3 "не совпадает с диапазоном Python 2" и функционально совпадает с Python 2 "xrange", я также приурочил следующее:

C:\**Python27_64**\python -mtimeit -n 5 -r 2 -s"cnt = 0" "for i in **xrange**(10000000): cnt += 1"
5 loops, best of 2: **320 msec** per loop

Легко видеть, что версия 3.3 почти в два раза медленнее версии 2.7, а Python 3.4 примерно на 10% медленнее, чем эта.

Мой вопрос: есть ли параметр или параметр среды, который исправляет это, или это просто неэффективный код, или интерпретатор делает больше для версии Python 3?


Кажется, что ответ Python 3 использует целые числа "бесконечной точности", которые раньше назывались "long" в Python 2.x по умолчанию "int", без какой-либо возможности использовать фиксированную длину бита Python 2 "int" и обрабатывает эту переменную длину "int", которая занимает дополнительное время, как описано в ответах и ​​комментариях ниже.

Возможно, Python 3.4 несколько медленнее, чем Python 3.3, из-за изменений в распределении памяти для поддержки синхронизации, что немного замедляет выделение/освобождение памяти, что, вероятно, является основной причиной того, что текущая версия "длинной" обработки работает медленнее.

4b9b3361

Ответ 1

Разница обусловлена ​​заменой типа int типом long. Очевидно, операции с длинными целыми значениями будут медленнее, поскольку операции long более сложны.

Если вы заставляете python2 использовать longs, установив cnt в 0L, разница исчезает:

$python2 -mtimeit -n5 -r2 -s"cnt=0L" "for i in range(10000000): cnt += 1L"
5 loops, best of 2: 1.1 sec per loop
$python3 -mtimeit -n5 -r2 -s"cnt=0" "for i in range(10000000): cnt += 1"
5 loops, best of 2: 686 msec per loop
$python2 -mtimeit -n5 -r2 -s"cnt=0L" "for i in xrange(10000000): cnt += 1L"
5 loops, best of 2: 714 msec per loop

Как вы можете видеть на моей машине, python3.4 работает быстрее, чем python2, используя range и используя xrange при использовании long s. Последний тест с python 2 xrange показывает, что разница в этом случае минимальна.

У меня нет установленного python3.3, поэтому я не могу сравнивать между 3.3 и 3.4, но насколько я знаю, ничего существенного не изменилось между этими двумя версиями (относительно range), поэтому тайминги должны быть о тоже самое. Если вы видите существенную разницу, попробуйте проверить сгенерированный байт-код с помощью модуля dis. Было внесено изменение в распределители памяти (PEP 445), но я не знаю, были ли изменены распределители памяти по умолчанию, а какие последствия были по производительности.

Ответ 2

Сводный ответ того, что я узнал из этого вопроса, может помочь другим, которые задаются вопросом о том же, что и я:

  • Причиной замедления является то, что все целочисленные переменные в Python 3.x теперь являются "бесконечной точностью", как тип, который раньше назывался "long" в Python 2.x, но теперь является единственным целым типом как определено PEP 237. В соответствии с этим документом "короткие" целые числа, которые имели битовую глубину базовой архитектуры, больше не существуют (или только внутренне).

  • Старые операции с "короткими" переменными могут выполняться достаточно быстро, потому что они могут напрямую использовать операционные операции с машинным кодом и оптимизировать распределение новых объектов "int", поскольку они всегда имеют одинаковый размер.

  • Тип "long" в настоящее время представлен только объектом класса, выделенным в памяти, так как он может превышать заданную фиксированную длину/бит памяти; поскольку эти представления объектов могут расти или сокращаться для различных операций и, следовательно, иметь переменный размер, им не может быть предоставлено фиксированное распределение памяти и оставлено там.

  • Эти "длинные" типы (в настоящее время) не используют полный размер слова машинной архитектуры, но резервируют бит (обычно бит знака), чтобы выполнять проверки переполнения, таким образом, "бесконечная точность" делится (в настоящее время ) в 15-битные/30-разрядные "цифры" для 32-разрядных/64-разрядных архитектур соответственно.

  • Многие из общих применений этих "длинных" целых чисел не будут требовать более одного (или, возможно, двух для 32-разрядных архитектур) "цифр" , поскольку диапазон одной "цифры" составляет около одного миллиарда /32768 для 64-разрядных/32-разрядных архитектур соответственно.

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

  • Наибольшее поражение производительности - это, вероятно, постоянное выделение/освобождение памяти, одна пара для каждого цикла целых операций , что довольно дорого, особенно когда Python перемещается для поддержки многопоточности с блокировками синхронизации (что, вероятно, почему Python 3.4 хуже 3.3).

  • В настоящее время реализация всегда обеспечивает достаточные "цифры" , выделяя одну дополнительную "цифру" выше фактического размера "цифр" , используемых для самого большого операнда, если есть вероятность, что он может "расти" , выполняя (которая может или не может фактически использовать эту дополнительную "цифру" ), а затем нормализует длину результата для учета фактического количества используемых "цифр" , которое может фактически оставаться таким же (или, возможно, "сокращаться" для некоторых операций); это делается путем простого уменьшения количества отсчетов в "длинной" структуре без нового распределения, поэтому может отбросить одну "цифру" пространства памяти, но сэкономить стоимость выполнения еще одного цикла выделения/освобождения.

  • Есть надежда на улучшение производительности:. Для многих операций можно предсказать, приведет ли операция к "росту" или нет, например, для добавления, которое просто необходимо для просмотра наиболее значимых битов (MSB), а операция не может расти, если обе MSB равны нулю, что будет иметь место для многих операций цикла/счетчика; вычитание не будет "расти" в зависимости от знаков и MSB двух операндов; сдвиг влево будет только "расти" , если MSB один; и т.д.

  • В тех случаях, когда оператор представляет собой нечто вроде "cnt + = 1" / "i + = step" и т.д. (открытие возможности операций для многих случаев использования), "на месте" , можно было бы назвать версию операций, которая будет выполнять соответствующие быстрые проверки и выделять только новый объект, если требуется "рост", в противном случае операция вместо первого операнда. Усложнение заключалось бы в том, что компилятору необходимо было бы создавать эти "на месте" байтовые коды, однако это уже было сделано, при этом были созданы соответствующие специальные операции "на месте" , текущий интерпретатор байтового кода направляет их в обычную версию, как описано выше, потому что они еще не реализованы (нулевые/нулевые значения в таблице).

  • Возможно, все, что нужно сделать, это написать версии этих "операций на месте" и заполнить их в таблице "long" методов, когда интерпретатор байтового кода уже находит и запускает их, если они существуют или незначительные изменения в таблице, чтобы заставить их называть их всем, что требуется.

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

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

Я считаю, что, избегая выделения/разметки памяти для этих распространенных случаев использования, производительность Python 3.x будет довольно близка к Python 2.7.

Значительная часть того, что я узнал, происходит из исходного файла Python 'C' для "длинного" объекта


EDIT_ADD: Опасайтесь, забыл, что если переменные иногда изменяемы, то замыкания на локальные переменные не работают или не работают без существенных изменений, а это означает, что вышеупомянутые операции inplace будут "ломаться", затворы. Казалось бы, лучшим решением было бы получить ускоренное выделение apace, работающее на "длинный", так же, как оно использовалось для короткого целого числа и делает для float, даже если только для случаев, когда "длинный" размер не изменяется ( который охватывает большую часть времени, например, для циклов и счетчиков в соответствии с вопросом). Это должно означать, что для обычного использования код не работает намного медленнее, чем Python 2.