Я заметил, что некоторые приложения или алгоритмы, построенные на языке программирования, скажем, C ++ / Rust, работают быстрее или быстрее, чем те, которые основаны, скажем, на Java / Node.js, работающие на той же машине. У меня есть несколько вопросов по этому поводу:
- Почему это происходит?
- Что управляет «скоростью» языка программирования?
- Это как-то связано с управлением памятью?
Я был бы очень признателен, если бы кто-то сломал это для меня.
programming-languages
compilers
evil_potato
источник
источник
Ответы:
В разработке и реализации языка программирования существует большое количество вариантов, которые могут повлиять на производительность. Я упомяну только несколько.
Каждый язык в конечном счете должен быть запущен путем выполнения машинного кода. «Скомпилированный» язык, такой как C ++, анализируется, декодируется и транслируется в машинный код только один раз, во время компиляции. «Интерпретируемый» язык, если он реализован прямым способом, декодируется во время выполнения, на каждом этапе, каждый раз. То есть каждый раз, когда мы запускаем оператор, интерпретатор должен проверять, является ли это условием «если-то-еще», или присваиванием и т. Д., И действовать соответственно. Это означает, что если мы повторяем цикл 100 раз, мы декодируем один и тот же код 100 раз, тратя впустую время. К счастью, переводчики часто оптимизируют это, например, с помощью системы компиляции точно в срок. (Вернее, не существует такого понятия, как «скомпилированный» или «интерпретируемый» язык - это свойство реализации, а не языка. Тем не менее,
Разные компиляторы / интерпретаторы выполняют разные оптимизации.
Если язык имеет автоматическое управление памятью, его реализация должна выполнять сборку мусора. Это требует затрат времени выполнения, но освобождает программиста от подверженной ошибкам задачи.
Язык может быть ближе к машине, что позволяет опытному программисту микрооптимизировать все и выжать из ЦП более высокую производительность. Тем не менее, можно утверждать, если это на самом деле полезно на практике, так как большинство программистов не очень микро-оптимизации и часто хороший язык высокого уровня может быть оптимизирована компилятором лучше, чем то, что средний программист будет делать. (Тем не менее, иногда отстранение от машины также может иметь свои преимущества! Например, у Haskell чрезвычайно высокий уровень, но благодаря своему выбору конструкции он может иметь очень легкие зеленые нити.)
Статическая проверка типов также может помочь в оптимизации. В динамически типизированном, интерпретируемом языке, каждый раз, когда кто-то вычисляет
x - y
, интерпретатор часто должен проверять,x,y
являются ли оба числа и (например) выдавать исключение в противном случае. Эта проверка может быть пропущена, если типы уже были проверены во время компиляции.Некоторые языки всегда сообщают об ошибках времени выполнения в здравом смысле. Если вы пишете
a[100]
на Java, гдеa
всего 20 элементов, вы получите исключение времени выполнения. В C это может вызвать неопределенное поведение, означающее, что программа может аварийно завершить работу, перезаписать некоторые случайные данные в памяти или даже выполнить что - либо еще (стандарт ISO C не устанавливает никаких ограничений). Это требует проверки во время выполнения, но обеспечивает гораздо более приятную семантику для программиста.Однако имейте в виду, что при оценке языка производительность - это еще не все. Не думай об этом. Это обычная ловушка, чтобы попытаться микрооптимизировать все, и все же не в состоянии обнаружить, что используется неэффективный алгоритм / структура данных. Кнут однажды сказал, что «преждевременная оптимизация - корень всего зла».
Не стоит недооценивать, насколько сложно написать программу правильно . Часто может быть лучше выбрать «более медленный» язык с более дружественной для человека семантикой. Кроме того, если есть какие-то конкретные критические для производительности части, они всегда могут быть реализованы на другом языке. В качестве примера, в конкурсе программ ICFP 2016 года победителями стали следующие языки:
Никто из них не использовал ни одного языка.
источник
Нет такой вещи, как «скорость» языка программирования. Существует только скорость конкретной программы, написанной конкретным программатором, исполняемым конкретной версией конкретной реализации конкретного механизма исполнения, работающего в конкретной среде.
Могут быть огромные различия в производительности при запуске одного и того же кода, написанного на одном и том же языке, на одной машине с использованием разных реализаций. Или даже используя разные версии одной и той же реализации. Например, запуск точно такого же теста 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-компиляцией и частичной оценкой.источник
int
s по соображениям производительности, несмотря на тот факт, что неограниченные целые числа, такие как те, что используются в Python, намного более математически естественны. Реализация неограниченных целых в аппаратных средствах не будет такой же быстрой, как целые числа фиксированного размера. Языки, которые пытаются скрыть детали реализации, нуждаются в комплексной оптимизации, чтобы приблизиться к наивным реализациям Си.Теоретически, если вы пишете код, который делает то же самое на двух языках, и компилируете оба с использованием «идеального» компилятора, производительность обоих должна быть одинаковой.
На практике есть несколько причин, почему производительность будет отличаться:
Некоторые языки сложнее оптимизировать.
Это включает, в частности, функции, которые делают код более динамичным (динамическая типизация, виртуальные методы, указатели функций), а также, например, сборку мусора.
Существуют различные способы сделать код, использующий такие функции, быстрым, но обычно он все равно оказывается, по крайней мере, несколько медленнее, чем без их использования.
Некоторые реализации языка должны выполнять компиляцию во время выполнения.
Это особенно относится к языкам с виртуальными машинами (такими как Java) и языкам, которые выполняют исходный код, без промежуточного шага для двоичного кода (например, JavaScript).
Такие языковые реализации должны выполнять больше работы во время выполнения, что влияет на производительность, особенно время запуска / производительность вскоре после запуска.
Языковые реализации намеренно тратят меньше времени на оптимизацию, чем могли бы.
Все компиляторы заботятся о производительности самого компилятора, а не только сгенерированного кода. Это особенно важно для компиляторов времени выполнения (JIT-компиляторов), где слишком долгая компиляция замедляет выполнение приложения. Но это относится и к преждевременным компиляторам, таким как C ++. Например, распределение регистров является NP-полной проблемой, поэтому нереально решить ее полностью, и вместо этого используются эвристики, которые выполняются в разумные сроки.
Различия в идиомах на разных языках.
Код, написанный в стиле, общем для определенного языка (идиоматический код) с использованием общих библиотек, может привести к разнице в производительности, потому что такой идиоматический код ведет себя слегка по-разному в каждом языке.
В качестве примера рассмотрим
vector[i]
C ++,list[i]
C # иlist.get(i)
Java. Код C ++, скорее всего, не проверяет диапазон и не выполняет виртуальные вызовы, код C # выполняет проверку диапазона, но нет виртуальных вызовов, а код Java выполняет проверку диапазона, и это виртуальный вызов. Все три языка поддерживают виртуальные и не виртуальные методы, и C ++ и C # могут включать проверку диапазона или избегать его при доступе к базовому массиву. Но стандартные библиотеки этих языков решили написать эти эквивалентные функции по-разному, и, как следствие, их производительность будет отличаться.В некоторых компиляторах могут отсутствовать некоторые оптимизации.
Авторы компиляторов имеют ограниченные ресурсы, поэтому они не могут реализовать все возможные оптимизации, даже игнорируя другие проблемы. И ресурсы, которые они имеют, могут быть направлены на одну область оптимизации больше, чем на другие. Как следствие, код, написанный на разных языках, может иметь разную производительность из-за различий в их компиляторах, даже если нет фундаментальной причины, по которой один язык должен быть быстрее или даже проще для оптимизации, чем другой.
источник
Это очень общий вопрос, но в вашем случае ответ может быть простым. C ++ компилируется в машинный код, где Java компилируется в байтовый код Java, который затем запускается на виртуальной машине Java (хотя есть также компиляция точно в срок, которая дополнительно компилирует байтовый код Java в собственный машинный код). Другим отличием может быть сборщик мусора, который представляет собой сервис, предоставляемый только Java.
источник
Ваш вопрос слишком общий, поэтому я боюсь, что не могу дать точный ответ, который вам нужен.
Для лучшего объяснения давайте рассмотрим платформы C ++ и .Net.
C ++, очень близкий к машинному коду и один из любимых в старое время (как более 10 лет назад?) Из-за производительности. Существует не так много ресурсов, необходимых для разработки и выполнения программы на C ++, даже с IDE, они считаются очень легкими и очень быстрыми. Вы также можете написать код C ++ в консоли и разработать игру оттуда. Что касается памяти и ресурсов, я примерно забыл, сколько ресурсов требуется компьютеру, но его нельзя сравнивать с языком программирования текущего поколения.
Теперь давайте посмотрим на .Net. Необходимым условием для разработки .Net является одна гигантская IDE, которая состоит не только из одного типа языков программирования. Даже если вы просто хотите разрабатывать, скажем, на C #, сама IDE будет включать в себя множество программных платформ по умолчанию, таких как J #, VB, mobile и т. Д. По умолчанию. Если вы не делаете пользовательскую установку и устанавливаете только то, что вам нужно, при условии, что у вас достаточно опыта, чтобы играть с установкой IDE.
Помимо установки самого программного обеспечения IDE, .Net также поставляется с огромным объемом библиотек и сред для облегчения доступа к любой платформе, которая нужна разработчикам и не нужна им.
Разработка в .Net может быть увлекательным занятием, потому что многие общие функции и компоненты доступны по умолчанию. Вы можете перетаскивать и использовать многие методы проверки, чтения файлов, доступа к базе данных и многое другое в IDE.
В результате это компромисс между компьютерными ресурсами и скоростью разработки. Эти библиотеки и фреймворки занимают память и ресурсы. Когда вы запускаете программу в .Net IDE, она может занять кучу времени, пытаясь скомпилировать библиотеки, компоненты и все ваши файлы. А когда вы выполняете отладку, вашему компьютеру требуется много ресурсов для управления процессом отладки в вашей IDE.
Обычно, для разработки .Net вам нужен хороший компьютер с процессором и процессором. В противном случае вы могли бы вообще не программировать. В этом аспекте платформа C ++ намного лучше, чем .Net. Хотя вам все еще нужен хороший компьютер, но емкость не будет слишком большой проблемой по сравнению с .Net.
Надеюсь, мой ответ поможет с вашим вопросом. Дайте мне знать, если вы хотите узнать больше.
РЕДАКТИРОВАТЬ:
Просто пояснение к моему основному вопросу: я в основном отвечаю на вопрос «Что определяет скорость программирования».
С точки зрения IDE, использование языка C ++ или .Net в относительной IDE не влияет на скорость написания кода, но влияет на скорость разработки, поскольку компилятору Visual Studio требуется больше времени для выполнения программы, но C ++ IDE намного легче и потреблять меньше ресурсов компьютера. Таким образом, в долгосрочной перспективе вы можете быстрее программировать с IDE-типом C ++ по сравнению с .Net IDE, который сильно зависит от библиотек и инфраструктуры. Если вы потратите время ожидания запуска среды IDE, ее компиляции, выполнения программы и т. Д., Это повлияет на скорость программирования. Если только «скорость программирования» на самом деле не фокусируется только на «скорости написания кода».
Количество библиотек и фреймворков в Visual Studio также потребляют ресурсы компьютера. Я не уверен, что это ответит на вопрос «управления памятью», но я просто хочу отметить, что Visual Studio IDE может потреблять много ресурсов при его запуске и, таким образом, замедлять общую «скорость программирования». Конечно, это не имеет ничего общего с «скоростью написания кода».
Как вы уже догадались, я не знаю слишком много о C ++, так как просто использую его в качестве примера, моя главная мысль о типе тяжелой IDE в Visual Studio, которая потребляет ресурсы компьютера.
Если я понял идею и вообще не ответил на вопрос для начинающих, прошу прощения за длинный пост. Но я бы посоветовал начинающему обсуждению прояснить вопрос и точно спросить, что ему нужно знать о «быстрее» и «медленнее».
источник