Повторяющаяся тема SE, которую я заметил во многих вопросах, - это постоянный аргумент, что C ++ быстрее и / или более эффективен, чем языки более высокого уровня, такие как Java. Противоположным аргументом является то, что современная JVM или CLR могут быть столь же эффективными благодаря JIT и т. Д. Для растущего числа задач, и что C ++ становится еще более эффективным, если вы знаете, что делаете и почему делаете вещи определенным образом. заслуживает повышения производительности. Это очевидно и имеет смысл.
Я хотел бы знать базовое объяснение (если есть такая вещь ...) относительно того, почему и как определенные задачи выполняются в C ++ быстрее, чем JVM или CLR? Это просто потому, что C ++ скомпилирован в машинный код, в то время как JVM или CLR все еще имеют накладные расходы на обработку JIT-компиляции во время выполнения?
Когда я пытаюсь исследовать эту тему, все, что я нахожу, это те же аргументы, которые я изложил выше, без какой-либо подробной информации о том, как именно понять, как C ++ может использоваться для высокопроизводительных вычислений.
источник
Ответы:
Это все о памяти (не JIT). «Преимущество JIT над C» в основном ограничивается оптимизацией виртуальных или не виртуальных вызовов с помощью встраивания, то, что BTB CPU уже усердно работает.
В современных машинах доступ к ОЗУ очень медленный (по сравнению с тем, что делает ЦП), что означает, что приложения, которые используют кеши в максимально возможной степени (что легче, когда используется меньше памяти), могут быть в сто раз быстрее, чем те, нет. И есть много способов, которыми Java использует больше памяти, чем C ++, и затрудняет написание приложений, которые полностью используют кеш:
Некоторые другие факторы, связанные с памятью, но не связанные с кэшем:
Некоторые из этих вещей являются компромиссами (отсутствие необходимости выполнять ручное управление памятью стоит потерять большую производительность для большинства людей), некоторые, вероятно, являются результатом попытки сохранить простоту Java, а некоторые - ошибки проектирования (хотя, возможно, только в ретроспективе а именно, UTF-16 был кодировкой фиксированной длины при создании Java, что делает решение о ее выборе гораздо более понятным).
Стоит отметить, что многие из этих компромиссов сильно отличаются для Java / JVM, чем для C # / CIL. .NET CIL имеет структуры ссылочного типа, распределение / передачу стека, упакованные массивы структур и обобщенные экземпляры типов.
источник
Частично, но в целом, при условии совершенно фантастического современного JIT-компилятора, надлежащий код C ++ по- прежнему имеет тенденцию работать лучше, чем код Java, по двум основным причинам:
1) Шаблоны C ++ предоставляют лучшие возможности для написания как общего, так и эффективного кода . Шаблоны предоставляют программисту C ++ очень полезную абстракцию с накладными расходами времени выполнения ZERO. (Шаблоны - это в основном утка во время компиляции.) Наоборот, лучшее, что вы получаете с обобщениями Java, - это в основном виртуальные функции. Виртуальные функции всегда имеют накладные расходы и обычно не могут быть встроены.
В целом, большинство языков, включая Java, C # и даже C, позволяют выбирать между эффективностью и универсальностью / абстракцией. Шаблоны C ++ дают вам оба (за счет более длительного времени компиляции.)
2) Тот факт, что стандарт C ++ мало что может сказать о бинарной компоновке скомпилированной программы C ++, дает компиляторам C ++ гораздо большую свободу действий, чем компилятору Java, что обеспечивает лучшую оптимизацию (иногда за счет большей сложности в отладке. На самом деле, сама природа спецификации языка Java обеспечивает снижение производительности в определенных областях. Например, вы не можете иметь непрерывный массив объектов в Java. Вы можете иметь только непрерывный массив указателей объектов(ссылки), что означает, что перебор массива в Java всегда влечет за собой косвенные издержки. Однако семантика значений в C ++ включает смежные массивы. Другим отличием является тот факт, что C ++ позволяет размещать объекты в стеке, в то время как Java этого не делает, а это означает, что на практике, поскольку большинство программ C ++ обычно размещают объекты в стеке, стоимость размещения часто близка к нулю.
Одной из областей, в которой C ++ может отставать от Java, является любая ситуация, когда в куче нужно разместить много небольших объектов. В этом случае система сборки мусора в Java, вероятно, приведет к лучшей производительности, чем стандартная,
new
иdelete
в C ++, потому что Java GC обеспечивает массовое освобождение. Но опять же, программист C ++ может компенсировать это, используя пул памяти или распределитель плит, тогда как программист Java не имеет возможности обратиться к шаблону выделения памяти, для которого среда выполнения Java не оптимизирована.Кроме того, посмотрите этот отличный ответ для получения дополнительной информации по этой теме.
источник
std::vector<int>
динамический массив предназначен только для целых чисел, и компилятор может оптимизировать его соответствующим образом . AC #List<int>
по-прежнему простоList
.List<int>
используетint[]
, а неObject[]
как Java. См stackoverflow.com/questions/116988/...vector<N>
где, для конкретного случаяvector<4>
, должна использоватьсяТо, что другие ответы (до сих пор 6), кажется, забыли упомянуть, но то, что я считаю очень важным для ответа на этот вопрос, является одной из самых базовых философий проектирования C ++, которая была сформулирована и использована Страуструпом со дня №1:
Вы не платите за то, что не используете.
Существуют и другие важные принципы проектирования, которые в значительной степени сформировали C ++ (например, вам не следует навязывать какую-то конкретную парадигму), но вы не платите за то, что вы не используете , прямо среди самых важных.
В своей книге «Дизайн и развитие C ++» (обычно называемой [D & E]), Страуструп описывает, в чем он нуждался, что заставило его в первую очередь придумать C ++. Мои собственные слова: для своей кандидатской диссертации (что-то связанное с сетевым моделированием, IIRC) он внедрил систему в SIMULA, которая ему очень понравилась, потому что язык очень хорошо позволял ему выражать свои мысли непосредственно в коде. Тем не менее, результирующая программа работала слишком медленно, и чтобы получить степень, он переписал эту вещь на BCPL, предшественнике языка C. Написание кода на BCPL, который он описывает как боль, но получающаяся программа была достаточно быстрой, чтобы доставить ее. результаты, которые позволили ему закончить докторскую диссертацию.
После этого он хотел язык, который позволял бы переводить реальные проблемы в код как можно более непосредственно, но также позволял бы коду быть очень эффективным.
В связи с этим он создал то, что позже станет C ++.
Таким образом, указанная выше цель - не просто один из нескольких фундаментальных принципов проектирования, она очень близка смыслу C ++. И это можно найти практически повсюду в языке: функции доступны только
virtual
тогда, когда вы хотите, чтобы они (потому что вызов виртуальных функций сопряжен с небольшими издержками), POD инициализируются автоматически только при явном запросе, исключения только снижают производительность, когда вы на самом деле выбросить их (тогда как это было явной целью разработки, чтобы позволить установку / очистку стековых фреймов быть очень дешевой), не запускать сборщик мусора, когда захочется, и т. д.C ++ явно решил не дать вам некоторые удобства ( «я должен сделать этот виртуальный метод здесь?») В обмен на исполнение ( "нет, у меня нет, и теперь компилятор может
inline
это и оптимизировать черт из все дело! "), и, что неудивительно, это действительно привело к повышению производительности по сравнению с более удобными языками.источник
Знаете ли вы исследовательский документ Google по этой теме?
Из заключения:
Это, по крайней мере, частичное объяснение, в смысле «потому что компиляторы реального мира C ++ производят более быстрый код, чем компиляторы Java с помощью эмпирических мер».
источник
Это не дубликат ваших вопросов, но принятый ответ отвечает на большинство ваших вопросов: современный обзор Java
Подводить итоги:
Таким образом, в зависимости от того, с каким другим языком вы сравниваете C ++, вы можете получить или не получить тот же ответ.
В C ++ у вас есть:
Это особенности или побочные эффекты определения языка, которые теоретически делают его более эффективным в отношении памяти и скорости, чем любой язык, который:
C ++ агрессивное встраивание компилятора уменьшает или устраняет множество косвенных ошибок. Возможность генерировать небольшой набор компактных данных делает его удобным для кеширования, если вы не распределяете эти данные по всей памяти, а не упаковываете их вместе (оба варианта возможны, C ++ просто позволяет вам выбирать). RAII делает поведение памяти C ++ предсказуемым, устраняя множество проблем в случае моделирования в реальном времени или полу-реального времени, которые требуют высокой скорости. Проблемы локальности, в общем, можно суммировать следующим образом: чем меньше программа / данные, тем быстрее выполнение. C ++ предоставляет различные способы убедиться, что ваши данные находятся там, где вы хотите (в пуле, массиве и т. Д.) И что они компактны.
Очевидно, есть другие языки, которые могут делать то же самое, но они просто менее популярны, потому что они не предоставляют столько инструментов абстракции, как C ++, поэтому они менее полезны во многих случаях.
источник
В основном речь идет о памяти (как сказал Майкл Боргвардт) с добавлением немного неэффективности JIT.
Единственное, что не упомянуто, это кеш - чтобы использовать кеш полностью, вам нужно, чтобы ваши данные располагались непрерывно (т.е. все вместе). Теперь с системой GC память распределяется по куче GC, что быстро, но по мере использования памяти GC будет регулярно включаться и удалять блоки, которые больше не используются, а затем сжимать оставшиеся вместе. Помимо очевидной медлительности перемещения этих используемых блоков, это означает, что используемые вами данные могут не слипаться. Если у вас есть массив из 1000 элементов, если вы не выделите их все сразу (а затем обновите их содержимое, а не удаляете и создаете новые, которые будут созданы в конце кучи), они будут разбросаны по всей куче, таким образом, требуется несколько обращений к памяти, чтобы прочитать их все в кэш процессора. Приложение AC / C ++, скорее всего, выделит память для этих элементов, а затем вы обновите блоки данными. (хорошо, есть структуры данных, такие как список, которые ведут себя больше как выделения памяти GC, но люди знают, что они медленнее, чем векторы).
Вы можете увидеть это в действии, просто заменив любые объекты StringBuilder на String ... Stringbuilders работают, предварительно выделяя память и заполняя ее, и это известный прием производительности для систем java / .NET.
Не забывайте, что парадигма «удалять старые и выделять новые копии» очень интенсивно используется в Java / C #, просто потому, что людям говорят, что благодаря GC выделение памяти происходит очень быстро, и поэтому модель рассеянной памяти используется повсеместно ( кроме строителей строк, конечно), поэтому все ваши библиотеки, как правило, тратят впустую память и используют ее много, ни одна из которых не получает преимущества от смежности. Обвините в этом шумиху вокруг GC - они сказали, что память свободна, смеется.
Сам GC, очевидно, является еще одним перфектным ударом - когда он запускается, он должен не только проходить через кучу, но и освобождать все неиспользуемые блоки, а затем запускать финализаторы (хотя раньше это делалось отдельно в следующий раз приложение остановлено уплотняется и обновляется ссылка на новое местоположение блока. Как видите, работы много!
Хиты производительности для памяти C ++ сводятся к выделению памяти - когда вам нужен новый блок, вы должны пройти кучу в поисках следующего свободного места, которое достаточно велико, с сильно фрагментированной кучей, это не так быстро, как сборщик мусора «просто выделите еще один блок в конце», но я думаю, что он не такой медленный, как вся работа, которую выполняет сжатие GC, и может быть уменьшен с помощью нескольких блоков кучи фиксированного размера (также называемых пулами памяти).
Это еще не все, например загрузка сборок из GAC, требующая проверки безопасности, проверки путей (включите sxstrace и просто посмотрите, к чему это приводит!), А также другие общие разработки, которые, по-видимому, гораздо более популярны в java / .net. чем C / C ++.
источник
«Это просто потому, что C ++ скомпилирован в ассемблерный / машинный код, в то время как Java / C # все еще имеют накладные расходы на компиляцию JIT во время выполнения? В основном да!
Заметим, что Java имеет больше накладных расходов, чем просто JIT-компиляция. Например, он делает гораздо больше проверок для вас (как это делает вещи, как
ArrayIndexOutOfBoundsExceptions
иNullPointerExceptions
). Сборщик мусора является еще одним значительным накладным расходом.Там довольно подробное сравнение здесь .
источник
Имейте в виду, что ниже приведено только сравнение различий между нативной и JIT-компиляцией, и оно не охватывает специфику какого-либо конкретного языка или фреймворков. Могут быть законные причины для выбора конкретной платформы за пределами этого.
Когда мы утверждаем, что нативный код работает быстрее, мы говорим о типичном случае использования нативно скомпилированного кода в сравнении с JIT-скомпилированным кодом, когда типичное использование JIT-скомпилированного приложения должно выполняться пользователем с немедленными результатами (например, нет сначала жду компилятора). В этом случае, я не думаю, что кто-то может с уверенностью заявить, что JIT-скомпилированный код может соответствовать или превосходить нативный код.
Давайте предположим, что у нас есть программа, написанная на каком-то языке X, и мы можем скомпилировать ее с собственным компилятором и снова с JIT-компилятором. Каждый рабочий процесс включает в себя одни и те же этапы, которые можно обобщить как (Код -> Промежуточное представление -> Машинный код -> Выполнение). Большая разница между двумя заключается в том, какие этапы видит пользователь, а какие - программист. При нативной компиляции программист видит все, кроме стадии выполнения, но с JIT-решением, компиляция в машинный код просматривается пользователем в дополнение к выполнению.
Заявление о том, что A быстрее, чем B , относится к времени, затраченному на выполнение программы, как это видит пользователь . Если мы предположим, что обе части кода работают одинаково на этапе выполнения, мы должны предположить, что рабочий процесс JIT медленнее для пользователя, поскольку он также должен видеть время T компиляции в машинный код, где T> 0. Так что чтобы любая возможность рабочего процесса JIT выполнять то же, что и собственный рабочий процесс, для пользователя, мы должны уменьшить время выполнения кода, чтобы выполнение + компиляция в машинный код было меньше, чем только этап выполнения родного рабочего потока. Это означает, что мы должны оптимизировать код лучше в JIT-компиляции, чем в нативной компиляции.
Это, однако, довольно невыполнимо, поскольку для выполнения необходимых оптимизаций для ускорения выполнения мы должны тратить больше времени на этап компиляции для машинного кода и, следовательно, любое время, которое мы экономим в результате оптимизированного кода, фактически теряется, так как мы добавляем его в сборник. Другими словами, «медлительность» решения на основе JIT не только из-за дополнительного времени для JIT-компиляции, но и кода, создаваемого этой компиляцией, работает медленнее, чем собственное решение.
Я буду использовать пример: распределение регистра. Поскольку доступ к памяти в несколько тысяч раз медленнее, чем доступ к регистрам, в идеале мы хотим использовать регистры везде, где это возможно, и иметь как можно меньше обращений к памяти, но у нас ограниченное количество регистров, и мы должны выливать состояние в память, когда нам нужно регистр. Если мы используем алгоритм распределения регистров, который требует 200 мс для вычисления, и в результате мы экономим 2 мс времени выполнения - мы не будем оптимально использовать время для JIT-компилятора. Такие решения, как алгоритм Чейтина, который может генерировать высоко оптимизированный код, не подходят.
Роль JIT-компилятора состоит в том, чтобы найти лучший баланс между временем компиляции и качеством создаваемого кода, однако с большим смещением на быстрое время компиляции, так как вы не хотите оставлять пользователя в ожидании. Производительность исполняемого кода ниже в случае JIT, поскольку собственный компилятор не ограничен (в значительной степени) временем при оптимизации кода, поэтому может свободно использовать лучшие алгоритмы. Вероятность того, что общая компиляция + выполнение для JIT-компилятора может превзойти только время выполнения для скомпилированного кода, фактически равна 0.
Но наши виртуальные машины не ограничиваются компиляцией JIT. Они используют опережающие методы компиляции, кэширование, горячую замену и адаптивную оптимизацию. Итак, давайте изменим наше утверждение, что производительность - это то, что видит пользователь, и ограничим его временем, затрачиваемым на выполнение программы (предположим, что мы скомпилировали AOT). Мы можем эффективно сделать исполняемый код эквивалентным нативному компилятору (или, может быть, лучше?). Большим преимуществом для виртуальных машин является то, что они могут создавать код более высокого качества, чем собственный компилятор, поскольку он имеет доступ к большему количеству информации - информации о выполняющемся процессе, например о том, как часто может выполняться определенная функция. Затем виртуальная машина может применить адаптивную оптимизацию к наиболее важному коду с помощью горячей замены.
Однако есть проблема с этим аргументом - он предполагает, что оптимизация на основе профилей и тому подобное является чем-то уникальным для виртуальных машин, что не соответствует действительности. Мы также можем применить его к собственной компиляции - скомпилировав наше приложение с включенным профилированием, записав информацию, а затем перекомпилировав приложение с этим профилем. Вероятно, также стоит отметить, что горячая замена кода - это не то, что может сделать только JIT-компилятор, мы можем сделать это для собственного кода - хотя решения на основе JIT для этого более доступны и намного проще для разработчика. Итак, главный вопрос: может ли виртуальная машина предложить нам некоторую информацию, которую не может дать нативная компиляция, которая может повысить производительность нашего кода?
Я не могу видеть это сам. Мы можем применить большинство методов типичной виртуальной машины и к собственному коду, хотя этот процесс более сложный. Точно так же мы можем применить любые оптимизации собственного компилятора обратно к виртуальной машине, которая использует компиляцию AOT или адаптивные оптимизации. Реальность такова, что разница между нативным кодом и виртуальной машиной не так велика, как мы привыкли верить. В конечном итоге они приводят к одному и тому же результату, но используют другой подход, чтобы достичь этого. ВМ использует итеративный подход для создания оптимизированного кода, когда собственный компилятор ожидает его с самого начала (и может быть улучшен с помощью итеративного подхода).
Программист C ++ может утверждать, что ему нужны оптимизации с самого начала, и он не должен ждать, пока виртуальная машина решит, как это сделать, если вообще будет. Это, вероятно, справедливо для нашей современной технологии, поскольку текущий уровень оптимизации в наших виртуальных машинах уступает тому, что могут предложить нативные компиляторы, но это может не всегда иметь место в случае улучшения решений AOT в наших виртуальных машинах и т. Д.
источник
Эта статья представляет собой краткое изложение постов в блоге, в которых сравнивается скорость c ++ и c #, а также проблемы, которые необходимо решить на обоих языках, чтобы получить высокопроизводительный код. Суть в том, что «ваша библиотека важнее всего, но если вы находитесь на c ++, вы можете преодолеть это». или «современные языки имеют лучшие библиотеки и, следовательно, получают более быстрые результаты с меньшими усилиями» в зависимости от вашего философского уклона.
источник
Я думаю, что реальный вопрос здесь не в том, "что быстрее?" но "у которого есть лучший потенциал для более высокой производительности?" С учетом этих терминов C ++ явно выигрывает - он скомпилирован в нативный код, нет JITting, это более низкий уровень абстракции и т. Д.
Это далеко от полной истории.
Поскольку C ++ компилируется, любые оптимизации компилятора должны выполняться во время компиляции, и оптимизации компилятора, которые подходят для одной машины, могут быть совершенно неверными для другой. Это также тот случай, когда любая глобальная оптимизация компилятора может и будет поддерживать одни алгоритмы или шаблоны кода перед другими.
С другой стороны, программа JITted будет оптимизировать во время JIT, поэтому она может использовать некоторые приемы, которые не может скомпилированная программа, и может выполнять очень специфические оптимизации для машины, на которой она работает, и кода, на котором она работает. Как только вы преодолеете начальные издержки JIT, в некоторых случаях он может быть быстрее.
В обоих случаях разумная реализация алгоритма и другие случаи, когда программист не будет глупым, вероятно, будет гораздо более значимым фактором, однако - например, вполне возможно написать полностью мертвый строковый код на C ++, который будет зависеть даже от интерпретируемый язык сценариев.
источник
-march=native
). - «это более низкий уровень абстракции» не совсем верно. C ++ использует такие же высокоуровневые абстракции, как и Java (или, фактически, более высокие: функциональное программирование, «метапрограммирование шаблонов»), он просто реализует абстракции менее «чисто», чем Java.JIT-компиляция фактически отрицательно влияет на производительность. Если вы разрабатываете «идеальный» компилятор и «идеальный» JIT-компилятор, первый вариант всегда выигрывает в производительности.
И Java, и C # интерпретируются на промежуточные языки, а затем компилируются в собственный код во время выполнения, что снижает производительность.
Но теперь разница не столь очевидна для C #: Microsoft CLR создает различный собственный код для разных процессоров, что делает код более эффективным для машины, на которой он работает, что не всегда выполняется компиляторами C ++.
PS C # написан очень эффективно и не имеет много уровней абстракции. Это не относится к Java, который не так эффективен. Таким образом, в этом случае, с его отличным CLR, программы на C # часто показывают лучшую производительность, чем программы на C ++. Чтобы узнать больше о .Net и CLR, взгляните на «CLR via C #» Джеффри Рихтера .
источник