Какие семантические особенности Python (и других динамических языков) способствуют его медлительности?

26

Я не очень хорошо знаю Python. Я пытаюсь более точно понять, какие именно особенности динамических языков (например, Python, Lua, Scheme, Perl, Ruby, ....) заставляют их реализации работать медленно.

В качестве примера, метатабельный механизм Lua 5.3 может интуитивно сделать Lua довольно медленным, но на практике, по слухам, Lua работает довольно быстро (и быстрее, чем Python).

Кроме того, у меня есть интуиция (возможно, неправильная), что, поскольку на современных процессорах память намного медленнее, чем необработанные вычисления (доступ к памяти с отсутствием кэша требует столько же времени, сколько сотням арифметических операций), динамическая проверка типов (как if (value->type != INTEGER_TAG) return;в С языка) может бежать довольно быстро.

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

(Я вроде проектирую динамический язык в моем мониторе MELT , и некоторые из них будут переведены на C)

Василий Старынкевич
источник
1
Советы по производительности Lua , в которых объясняется, почему некоторые программы Lua работают медленно и как их исправить.
Роберт Харви

Ответы:

24

Какие семантические особенности Python (и других динамических языков) способствуют его медлительности?

Никто.

Выполнение языковых реализаций зависит от денег, ресурсов и кандидатских диссертаций, а не от особенностей языка. само намного более динамичен, чем Smalltalk, и немного более динамичен, чем Python, Ruby, ECMAScript или Lua, и имеет виртуальную машину, которая превосходит все существующие виртуальные машины Lisp и Smalltalk (на самом деле дистрибутив Self поставляется с небольшим интерпретатором Smalltalk, написанным на Self и даже это было быстрее, чем большинство существующих виртуальных машин Smalltalk), и было конкурентоспособным, а иногда даже быстрее, чем реализации C ++ того времени.

Затем Sun прекратила финансирование Self, а IBM, Microsoft, Intel и Co. начали финансировать C ++, и эта тенденция изменилась. Разработчики Self покинули Sun, чтобы основать свою собственную компанию, где они использовали технологию, разработанную для Self VM, для создания одной из самых быстрых виртуальных машин Smalltalk (Animorphic VM), а затем Sun выкупила эту компанию и слегка измененную версию. эта Smalltalk VM теперь более известна под названием «HotSpot JVM». По иронии судьбы, программисты Java смотрят на динамические языки свысока как на «медленные», хотя на самом деле Javaбыл медленным, пока не принял технологию динамического языка. (Да, верно: JSM HotSpot по сути является виртуальной машиной Smalltalk. Верификатор байт-кода выполняет большую проверку типов, но как только байт-код принят верификатором, ВМ, и особенно оптимизатор и JIT, на самом деле этого не делают. большой интерес со статическими типами!)

CPython просто не делает много вещей, которые делают динамические языки (или, скорее, динамическую диспетчеризацию) быстрыми: динамическая компиляция (JIT), динамическая оптимизация, умозрительная вставка, адаптивная оптимизация, динамическая де-оптимизация, обратная связь / вывод динамического типа. Есть также проблема в том, что почти все ядро ​​и стандартная библиотека написаны на C, что означает, что даже если вы вдруг сделаете Python 100x быстрее, это вам не сильно поможет, потому что что-то вроде 95% кода, выполняемого Программа на Python - это C, а не Python. Если бы все было написано на Python, даже умеренное ускорение привело бы к лавинному эффекту, когда алгоритмы становятся быстрее, а базовые структуры данных - быстрее, но, конечно, основные структуры данных также используются в алгоритмах, а также основные алгоритмы и основные данные. структуры используются везде,

Есть несколько вещей, которые заведомо плохи для ОО-языков с управлением памятью (динамических или нет) в современных системах. Виртуальная память и защита памяти могут быть причиной снижения производительности сборки мусора в частности и производительности системы в целом. И в языке, безопасном для памяти, это совершенно не нужно: зачем защищать от недопустимых обращений к памяти, если в языке нет доступа к памяти с самого начала? Azul решил использовать современные мощные MMU (Intel Nehalem и новее и эквивалент AMD), чтобы помочь сборке мусора вместо того, чтобы препятствовать ему, но даже несмотря на то, что он поддерживается процессором, современные подсистемы памяти основных ОС недостаточно мощны чтобы разрешить это (именно поэтому виртуальная машина Azul работает виртуально на голом металле, кроме ОС, не в ней).

В проекте ОС Singularity Microsoft измерила влияние ~ 30% на производительность системы при использовании защиты MMU вместо системы типов для разделения процессов.

Еще одна вещь, которую заметил Азул при создании своих специализированных процессоров Java, заключалась в том, что современные основные процессоры сосредотачиваются на совершенно неправильных вещах, пытаясь снизить стоимость промахов кэша: они пытаются уменьшить количество промахов кэша с помощью таких вещей, как прогнозирование ветвлений, предварительная выборка памяти, и так далее. Но в сильно полиморфной ОО-программе шаблоны доступа в основном псевдослучайные, предсказать просто нечего. Итак, все эти транзисторы просто потрачены впустую, и вместо этого нужно снизить стоимость каждого отдельного промаха кеша. (Общая стоимость равна #misses * cost, основной поток пытается прекратить работу первого, Azul - второго.) Java Compute Accelerator от Azul может иметь 20000 одновременных промахов кэша в полете и все же добиться прогресса.

Когда Azul начал, они подумали, что возьмут некоторые готовые компоненты ввода / вывода и спроектируют свое собственное специализированное ядро ​​ЦП, но то, что им действительно нужно было сделать, было с точностью до наоборот: они взяли довольно стандартную готовую версию. полка 3-адресное ядро ​​RISC и разработали собственный контроллер памяти, MMU и подсистему кеша.

tl; dr : «медлительность» Python является не свойством языка, а а) его наивной (основной) реализацией и б) тем фактом, что современные ЦП и ОС специально разработаны для обеспечения быстрой работы С, а также функции, которые они иметь для C либо не помогает (кеш) или даже активно ухудшает (виртуальная память) производительность Python.

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

Йорг Миттаг
источник
У вас есть ссылка или ссылка на Azul?
Василий Старынкевич
4
Я не согласен с тем, что семантика языка не влияет на его способность эффективно реализовываться. Да, хорошая реализация JIT с первоклассными оптимизацией и анализом может значительно улучшить производительность языка, но в конце концов есть определенные аспекты семантики, которые неизбежно окажутся узкими местами. Будь то требование C для строгого совмещения имен указателей или требование Python, чтобы операции со списками выполнялись атомарно, существуют определенные семантические решения, которые неизбежно приводят к снижению производительности некоторых приложений.
Жюль
1
Как в стороне ... у вас есть ссылка на это 30% улучшение для Singularity? Я много лет являюсь сторонником ОС на основе языковой защиты, но никогда раньше не видел эту цифру и считаю ее поразительной (цифры, на которые я смотрел в прошлом, были ближе к 10%), и удивляюсь, что они сделали, чтобы получить такое большое улучшение ...
Жюль
5
@MasonWheeler: Потому что есть только дрянные реализации Python. Ни один разработчик Python не потратил даже крошечную долю денег, людей, исследований и ресурсов, потраченных IBM, Sun, Oracle, Google и Co на J9, JRockit, HotSpot и Co. Все 5 реализаций Python вместе взятые, вероятно, даже не иметь персонал, который Oracle тратит только на сборщик мусора. IBM работает над реализацией Python, основанной на Eclipse OMR (компонентная структура VM с открытым исходным кодом, извлеченная из J9), я готов поспорить, что ее производительность будет на порядок выше J9
Йорг Миттаг,
2
Для записи C медленнее по сравнению с Fortran для числовой работы, поскольку Fortran применяет строгие псевдонимы, поэтому оптимизатор может быть более агрессивным.
Михаил Шопсин
8

Хотя текущая реализация Python (которой не хватает многих оптимизаций, выполняемых другими динамическими языками, например, современными реализациями Javascript и, как вы указываете, Lua), является источником большинства его проблем, у него действительно есть некоторые семантические проблемы, которые делают его для реализации трудно конкурировать с другими языками, по крайней мере, в определенных областях. Некоторые из них заслуживают особого внимания:

  • Операции со списком и словарем требуются для атомарного определения языка. Это означает, что если JIT-компилятор не может доказать, что ни одна ссылка на объект списка не ускользнула из его текущего потока (анализ, который во многих случаях труден, а в общем случае невозможен), он должен обеспечить сериализацию доступа к объекту (например, через блокировку). Реализация CPython решает эту проблему, используя пресловутую «глобальную блокировку интерпретатора», которая препятствует эффективному использованию кода Python в многопроцессорных средах с многопоточными методами, и, хотя возможны и другие решения, все они имеют проблемы с производительностью.

  • Python не имеет механизма для указания использования объектов-значений; все обрабатывается по ссылке, добавляя дополнительную косвенность, где это не обязательно требуется. Хотя JIT-компилятор может в некоторых случаях выводить объекты-значения и автоматически оптимизировать это, в целом это невозможно, и поэтому код, который недостаточно тщательно написан для обеспечения оптимизации, возможен (что отчасти является черным искусством) будет страдать.

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

Жюль
источник
3
Последняя точка может быть смягчена путем evalзапуска перекомпиляции и / или де-оптимизации.
Йорг Миттаг,
4
Кстати, это не уникально для Python. Java (или, скорее, JVM) имеет динамическую загрузку кода и динамическое связывание, поэтому анализ иерархии классов также эквивалентен решению проблемы остановки. Тем не менее, HotSpot счастливо спекулятивно включает полиморфные методы, и если что-то в иерархии классов изменится, ну, это просто вернет их обратно.
Йорг Миттаг,