Почему некоторые языки программирования «быстрее» или «медленнее», чем другие?


82

Я заметил, что некоторые приложения или алгоритмы, построенные на языке программирования, скажем, C ++ / Rust, работают быстрее или быстрее, чем те, которые основаны, скажем, на Java / Node.js, работающие на той же машине. У меня есть несколько вопросов по этому поводу:

  1. Почему это происходит?
  2. Что управляет «скоростью» языка программирования?
  3. Это как-то связано с управлением памятью?

Я был бы очень признателен, если бы кто-то сломал это для меня.


18
Обратите внимание, что, в частности, для JVM и CLR были проведены обширные исследования по оптимизации виртуальных машин, и они могут превзойти скомпилированный C ++ во многих обстоятельствах - но наивный сравнительный анализ легко сделать неправильно.
Хрилис - на забастовке -


26
Тем не менее, объединение Java и всего, что связано с Javascript, оскорбительно.
Рафаэль

5
Я разрываюсь между закрытием этого как слишком широкого (учебники могут быть написаны на соответствующие темы!) Или дублирования . Голосование сообщества, пожалуйста!
Рафаэль

4
vzn

Ответы:


96

В разработке и реализации языка программирования существует большое количество вариантов, которые могут повлиять на производительность. Я упомяну только несколько.

Каждый язык в конечном счете должен быть запущен путем выполнения машинного кода. «Скомпилированный» язык, такой как C ++, анализируется, декодируется и транслируется в машинный код только один раз, во время компиляции. «Интерпретируемый» язык, если он реализован прямым способом, декодируется во время выполнения, на каждом этапе, каждый раз. То есть каждый раз, когда мы запускаем оператор, интерпретатор должен проверять, является ли это условием «если-то-еще», или присваиванием и т. Д., И действовать соответственно. Это означает, что если мы повторяем цикл 100 раз, мы декодируем один и тот же код 100 раз, тратя впустую время. К счастью, переводчики часто оптимизируют это, например, с помощью системы компиляции точно в срок. (Вернее, не существует такого понятия, как «скомпилированный» или «интерпретируемый» язык - это свойство реализации, а не языка. Тем не менее,

Разные компиляторы / интерпретаторы выполняют разные оптимизации.

Если язык имеет автоматическое управление памятью, его реализация должна выполнять сборку мусора. Это требует затрат времени выполнения, но освобождает программиста от подверженной ошибкам задачи.

Язык может быть ближе к машине, что позволяет опытному программисту микрооптимизировать все и выжать из ЦП более высокую производительность. Тем не менее, можно утверждать, если это на самом деле полезно на практике, так как большинство программистов не очень микро-оптимизации и часто хороший язык высокого уровня может быть оптимизирована компилятором лучше, чем то, что средний программист будет делать. (Тем не менее, иногда отстранение от машины также может иметь свои преимущества! Например, у Haskell чрезвычайно высокий уровень, но благодаря своему выбору конструкции он может иметь очень легкие зеленые нити.)

Статическая проверка типов также может помочь в оптимизации. В динамически типизированном, интерпретируемом языке, каждый раз, когда кто-то вычисляет x - y, интерпретатор часто должен проверять, x,yявляются ли оба числа и (например) выдавать исключение в противном случае. Эта проверка может быть пропущена, если типы уже были проверены во время компиляции.

Некоторые языки всегда сообщают об ошибках времени выполнения в здравом смысле. Если вы пишете a[100]на Java, где aвсего 20 элементов, вы получите исключение времени выполнения. В C это может вызвать неопределенное поведение, означающее, что программа может аварийно завершить работу, перезаписать некоторые случайные данные в памяти или даже выполнить что - либо еще (стандарт ISO C не устанавливает никаких ограничений). Это требует проверки во время выполнения, но обеспечивает гораздо более приятную семантику для программиста.

Однако имейте в виду, что при оценке языка производительность - это еще не все. Не думай об этом. Это обычная ловушка, чтобы попытаться микрооптимизировать все, и все же не в состоянии обнаружить, что используется неэффективный алгоритм / структура данных. Кнут однажды сказал, что «преждевременная оптимизация - корень всего зла».

Не стоит недооценивать, насколько сложно написать программу правильно . Часто может быть лучше выбрать «более медленный» язык с более дружественной для человека семантикой. Кроме того, если есть какие-то конкретные критические для производительности части, они всегда могут быть реализованы на другом языке. В качестве примера, в конкурсе программ ICFP 2016 года победителями стали следующие языки:

1   700327  Unagi                       Java,C++,C#,PHP,Haskell
2   268752  天羽々斬                     C++, Ruby, Python, Haskell, Java, JavaScript
3   243456  Cult of the Bound Variable  C++, Standard ML, Python

Никто из них не использовал ни одного языка.


81
Одна версия всей цитаты из Кнута : «Программисты тратят огромное количество времени на размышления или беспокойство по поводу скорости некритических частей своих программ, и эти попытки повышения эффективности на самом деле оказывают сильное негативное влияние при рассмотрении вопросов отладки и обслуживания. Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла. Но мы не должны упускать наши возможности в эти критические 3% ».
Дерек Элкинс

6
Также некоторые языки гарантируют, казалось бы, невинные вещи, которые могут повлиять на способность компилятора оптимизировать, см. Строгий псевдоним в C ++ против FORTRAN
PlasmaHH

9
Присоединяйся, чтобы я мог поднять комментарий @DerekElkins. Слишком часто контекст этой цитаты отсутствует, иногда даже приводит к выводу, что она выступает за то, что вся оптимизация является злом.
труба

9
@DerekElkins По иронии судьбы, вы пропустили, пожалуй, самую важную часть: два предложения сразу после. «Хороший программист не будет погружен в самоуспокоенность из-за таких рассуждений, ему будет разумно внимательно посмотреть на критический код, но только после того, как этот код будет идентифицирован. Часто априори делать ошибки в отношении того, какие части Программы действительно важны, так как универсальный опыт программистов, использующих инструменты измерения, заключается в том, что их интуитивные догадки не удаются ». PDF p268
8bittree

9
@DerekElkins Чтобы было ясно, я не хочу вкладывать слова в ваши слова о том, что вы заявляете об этом, но слишком часто я сталкиваюсь с людьми, добавляющими немного в базовую цитату и думающими, что Кнут выступает за преждевременную оптимизацию 3 В% случаев, когда в полной статье проясняется, что он всегда выступает против преждевременной оптимизации, почти всегда выступает против небольших оптимизаций, но выступает за небольшие оптимизации в разделах, оцениваемых как критические.
8bittree

19

Что управляет «скоростью» языка программирования?

Нет такой вещи, как «скорость» языка программирования. Существует только скорость конкретной программы, написанной конкретным программатором, исполняемым конкретной версией конкретной реализации конкретного механизма исполнения, работающего в конкретной среде.

Могут быть огромные различия в производительности при запуске одного и того же кода, написанного на одном и том же языке, на одной машине с использованием разных реализаций. Или даже используя разные версии одной и той же реализации. Например, запуск точно такого же теста 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-компиляцией и частичной оценкой.


4
Хотя технически языки не являются по своей природе быстрыми, в некоторых языках больше внимания уделяется тому, чтобы программист мог быстро создавать код. C в первую очередь оптимизирован для процессоров, а не наоборот. Например, C выбрал фиксированный размер ints по соображениям производительности, несмотря на тот факт, что неограниченные целые числа, такие как те, что используются в Python, намного более математически естественны. Реализация неограниченных целых в аппаратных средствах не будет такой же быстрой, как целые числа фиксированного размера. Языки, которые пытаются скрыть детали реализации, нуждаются в комплексной оптимизации, чтобы приблизиться к наивным реализациям Си.
Gmatht

1
C имеет историю - он был создан, чтобы сделать систему, написанную на ассемблере, переносимой на другие системы. Поэтому первоначальная цель заключалась в том, чтобы предоставить «переносной ассемблер» для Unix, и он был очень хорошо спроектирован. С 1980 по 1995 год он так хорошо себя чувствовал, что имел критическую массу, когда появилась Windows 95.
Турбьерн Равн Андерсен

1
Я бы не согласился с комментарием о ресурсах. Важно не количество людей в команде, а уровень мастерства лучших людей в команде.
Майкл Кей

«В Microsoft, вероятно, работает больше людей, готовящих кофе для своих программистов компиляторов, чем во всем сообществе PHP, Ruby и Python вместе работают люди на своих виртуальных машинах». Полагаю, это зависит от того, насколько вы готовы расширить термин «программист компилятора» и сколько вы включаете с этим (Microsoft разрабатывает много компиляторов). Например, только команда компилятора VS C ++ является относительно небольшим AFAIK.
Куб

7

Теоретически, если вы пишете код, который делает то же самое на двух языках, и компилируете оба с использованием «идеального» компилятора, производительность обоих должна быть одинаковой.

На практике есть несколько причин, почему производительность будет отличаться:

  1. Некоторые языки сложнее оптимизировать.

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

    Существуют различные способы сделать код, использующий такие функции, быстрым, но обычно он все равно оказывается, по крайней мере, несколько медленнее, чем без их использования.

  2. Некоторые реализации языка должны выполнять компиляцию во время выполнения.

    Это особенно относится к языкам с виртуальными машинами (такими как Java) и языкам, которые выполняют исходный код, без промежуточного шага для двоичного кода (например, JavaScript).

    Такие языковые реализации должны выполнять больше работы во время выполнения, что влияет на производительность, особенно время запуска / производительность вскоре после запуска.

  3. Языковые реализации намеренно тратят меньше времени на оптимизацию, чем могли бы.

    Все компиляторы заботятся о производительности самого компилятора, а не только сгенерированного кода. Это особенно важно для компиляторов времени выполнения (JIT-компиляторов), где слишком долгая компиляция замедляет выполнение приложения. Но это относится и к преждевременным компиляторам, таким как C ++. Например, распределение регистров является NP-полной проблемой, поэтому нереально решить ее полностью, и вместо этого используются эвристики, которые выполняются в разумные сроки.

  4. Различия в идиомах на разных языках.

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

    В качестве примера рассмотрим vector[i]C ++, list[i]C # и list.get(i)Java. Код C ++, скорее всего, не проверяет диапазон и не выполняет виртуальные вызовы, код C # выполняет проверку диапазона, но нет виртуальных вызовов, а код Java выполняет проверку диапазона, и это виртуальный вызов. Все три языка поддерживают виртуальные и не виртуальные методы, и C ++ и C # могут включать проверку диапазона или избегать его при доступе к базовому массиву. Но стандартные библиотеки этих языков решили написать эти эквивалентные функции по-разному, и, как следствие, их производительность будет отличаться.

  5. В некоторых компиляторах могут отсутствовать некоторые оптимизации.

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


Некоторые языка не имеют компилятор. Для некоторых языков не может быть компилятора ( ссылка ).
Рафаэль

3
Для некоторых языков не может быть статического компилятора. На динамическую компиляцию не влияет неразрешимость статических свойств.
Йорг Миттаг

Если языки достаточно разные, у них нет «кода, который делает то же самое». Они могут иметь код, который выдает тот же вывод или наблюдаемое пользователем поведение, что не одно и то же. Поэтому я не согласен с вашей предпосылкой.
einpoklum - восстановить Monica

@einpoklum Я не вижу различий. За некоторыми исключениями (такими как синхронизация потоков), которые, я думаю, здесь не интересны, спецификации языка обычно допускают любые оптимизации, которые сохраняют наблюдаемое поведение. Какое поведение вы имеете в виду, которое не доступно для наблюдения, но не может быть оптимизировано?
svick

Теоретически любой интерпретируемый язык может быть «скомпилирован» путем генерации EXE-файла, состоящего из интерпретатора + исходного кода программы.
dan04

1

Это очень общий вопрос, но в вашем случае ответ может быть простым. C ++ компилируется в машинный код, где Java компилируется в байтовый код Java, который затем запускается на виртуальной машине Java (хотя есть также компиляция точно в срок, которая дополнительно компилирует байтовый код Java в собственный машинный код). Другим отличием может быть сборщик мусора, который представляет собой сервис, предоставляемый только Java.


2
Это слишком упрощенный ответ. Есть настройки, в которых Java работает быстрее.
Рафаэль

4
Существуют также реализации Java, которые компилируются в машинный код и интерпретируемые реализации C ++. И что такое «машинный код» в любом случае, если у меня есть процессор picoJava, который непосредственно выполняет байт-код JVM, и у меня на JVM работает интерпретатор JPC x86, то что делает объектный код x86 «машинным кодом», а байт-код JVM - нет?
Йорг Миттаг

2
Nitpick: не только Java обеспечивает сборку мусора ... и даже если вы рассматриваете только C ++ и Java, некоторые программы и библиотеки C ++ имеют средства для сборки мусора.
einpoklum - восстановить Monica

Компиляция в машинный код обычно повышает производительность. Однако в некоторых ограниченных случаях высококачественный интерпретатор может превзойти наивный компилятор, работающий точно в срок.
ДепрессияДаниэль

@ JörgWMittag Хитрость заключается в том, чтобы скомпилировать собственный машинный код - машинный код, который понимает хост-процессор, - а затем непосредственно перейти к вызываемому коду, чтобы он мог выполняться без интерпретации. Это будет x86 на чипе x86 и байт-коды JVM на процессоре picoJava.
ДепрессияДаниэль

-5

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

Для лучшего объяснения давайте рассмотрим платформы 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, которая потребляет ресурсы компьютера.

Если я понял идею и вообще не ответил на вопрос для начинающих, прошу прощения за длинный пост. Но я бы посоветовал начинающему обсуждению прояснить вопрос и точно спросить, что ему нужно знать о «быстрее» и «медленнее».


6
Просто для записи, для разработки .NET требуется только .NET SDK (и, для выполнения, сама среда выполнения .NET). Вы можете использовать простой текстовый редактор и вызывать компилятор из командной строки. IDE (я предполагаю, что вы имеете в виду Visual Studio) не требуется, хотя она, безусловно, помогает в любом значительном проекте (Intellisense, отладчик и т. Д.). Есть также более легкие IDE, такие как SharpDevelop с меньшим количеством наворотов.
MetalMikester

16
Конечно, вы можете разрабатывать в .Net без IDE. Но я не понимаю, насколько IDE имеет отношение к скорости написания кода на языке.
svick

8
@MetalMikester: И, конечно же, Visual Studio - это интегрированная среда разработки для C ++ для Windows.
Йорг Миттаг,

3
А компиляция кода C ++ в .Net - это всего лишь один переключатель компилятора; Предполагаемая пропасть - мосты одним щелчком мыши в визуальной студии.
MSalters

1
Я совсем не уверен, что назвал бы современный C ++ "очень близким к машинному коду". Оригинальный C, да; он задумывался как переносимый ассемблер и оставался очень близким к этому (хотя стандартная библиотека выросла, и различные компиляторы предлагают дополнения к самому языку, что уводит его от его истоков). Оригинальный C ++ (C With Classes), возможно; переписать вариант C, который включает в себя классы для чистого C, не так уж и сложно, в этот момент применимо предыдущее обсуждение. Но современный C ++, с шаблонами, полиморфизмом и всем остальным под солнцем? Это вряд ли "очень близко к машинному коду".
CVn
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.