Что управляет «скоростью» языка программирования?
Нет такой вещи, как «скорость» языка программирования. Существует только скорость конкретной программы, написанной конкретным программатором, исполняемым конкретной версией конкретной реализации конкретного механизма исполнения, работающего в конкретной среде.
Могут быть огромные различия в производительности при запуске одного и того же кода, написанного на одном и том же языке, на одной машине с использованием разных реализаций. Или даже используя разные версии одной и той же реализации. Например, запуск точно такого же теста ECMAScript на той же машине с использованием версии SpiderMonkey от 10 лет назад по сравнению с версией этого года, вероятно, приведет к увеличению производительности где-то между 2 × –5 ×, возможно, даже 10 ×. Означает ли это, что ECMAScript в 2 раза быстрее, чем ECMAScript, потому что запуск одной и той же программы на той же машине в 2 раза быстрее с новой реализацией? Это не имеет смысла.
Это как-то связано с управлением памятью?
На самом деле, нет.
Почему это происходит?
Ресурсы. Деньги. В Microsoft, вероятно, работает больше людей, готовящих кофе для своих программистов компиляторов, чем все сообщество PHP, Ruby и Python, объединяющее людей, работающих на их виртуальных машинах.
Для более или менее любой функции языка программирования, которая каким-то образом влияет на производительность, также существует решение. Например, C (я использую здесь C как замену для класса похожих языков, некоторые из которых существовали даже до C) не является безопасным для памяти, так что несколько программ на C, работающих одновременно, могут попрать память друг друга. Итак, мы изобретаем виртуальную память и заставляем все программы на C проходить через уровень косвенности, чтобы они могли притворяться, что они единственные, работающие на машине. Однако, это медленно, и поэтому мы изобретаем MMU и внедряем виртуальную память аппаратно, чтобы ускорить ее.
Но! Безопасные для памяти языки не нуждаются во всем этом! Наличие виртуальной памяти никоим образом не помогает им. На самом деле, это еще хуже: виртуальная память не только не помогает безопасным для памяти языкам, но и виртуальная память, даже если она реализована на аппаратном уровне, по-прежнему влияет на производительность. Это может быть особенно вредно для производительности сборщиков мусора (именно этим пользуется значительное количество реализаций безопасных для памяти языков).
Другой пример: современные универсальные центральные процессоры общего назначения используют сложные приемы, чтобы уменьшить частоту пропадания кэша. Многие из этих уловок сводятся к попыткам предсказать, какой код будет выполняться и какая память понадобится в будущем. Однако для языков с высокой степенью полиморфизма во время выполнения (например, ОО-языков) предсказать эти шаблоны доступа действительно очень сложно.
Но есть и другой способ: общая стоимость пропущенных кешей - это количество пропущенных кешей, умноженное на стоимость отдельных пропусков кеша. Основные процессоры пытаются уменьшить количество промахов, но что, если вы могли бы снизить стоимость отдельного промаха?
Процессор Azul Vega-3 был специально разработан для запуска виртуализированных виртуальных машин Java, и он имел очень мощный MMU с некоторыми специализированными инструкциями для помощи в сборе мусора и обнаружении побега (динамический эквивалент статического анализа побега), а также мощные контроллеры памяти и всю систему в целом. может все же добиться прогресса с более чем 20000 незавершенных промахов кэша в полете К сожалению, как и большинство процессоров для конкретных языков, его дизайн был просто израсходован и выкорчеван «гигантами» Intel, AMD, IBM и им подобными.
Архитектура ЦП - это только один пример, который влияет на то, насколько легко или сложно получить высокопроизводительную реализацию языка. Такой язык, как C, C ++, D, Rust, который хорошо подходит для современной модели программирования ЦП, будет легче создать быстрее, чем язык, который должен «бороться» и обходить ЦП, такой как Java, ECMAScript, Python, Ruby PHP.
На самом деле, это все вопрос денег. Если вы тратите равные суммы денег на разработку высокопроизводительного алгоритма в ECMAScript, высокопроизводительную реализацию ECMAScript, высокопроизводительную операционную систему, разработанную для ECMAScript, высокопроизводительный ЦП, разработанный для ECMAScript, как было потрачено за последние десятилетия, чтобы заставить C-подобные языки работать быстро, тогда вы, вероятно, увидите равную производительность. Просто в настоящее время гораздо больше денег было потрачено на быстрое создание C-подобных языков, чем на быстрые языки, подобные ECMAScript, и предположения о C-подобных языках встраиваются во весь стек от MMU и CPU до операционных систем и системы виртуальной памяти до библиотек и фреймворков.
Лично я больше всего знаком с Ruby (который обычно считается «медленным языком»), поэтому приведу два примера: Hash
класс (одна из центральных структур данных в Ruby, словарь значений ключей) в Rubinius Реализация Ruby написана на 100% чистом Ruby и имеет примерно ту же производительность, что иHash
класс в YARV (наиболее широко используемая реализация), который написан на C. И есть библиотека манипулирования изображениями, написанная как расширение C для YARV, которая также имеет (медленную) чистую Ruby «резервную версию» для реализаций, которые не не поддерживает C, который использует тонну высокодинамичных и отражающих трюков Ruby; экспериментальная ветка JRuby, использующая инфраструктуру интерпретатора Truffle AST и инфраструктуру компиляции Graal JIT от Oracle Labs, может выполнить эту чистую «резервную версию» Ruby так же быстро, как YARV может выполнить исходную высокооптимизированную версию C. Этого просто (ну, во всяком случае, кроме) достигают некоторые действительно умные люди, делающие действительно умные вещи с динамической оптимизацией во время выполнения, JIT-компиляцией и частичной оценкой.