Производительность C ++ по сравнению с Java / C #


119

Насколько я понимаю, C / C ++ создает собственный код для работы на конкретной архитектуре компьютера. И наоборот, такие языки, как Java и C #, работают поверх виртуальной машины, которая абстрагируется от собственной архитектуры. Логически казалось бы невозможным для Java или C # соответствовать скорости C ++ из-за этого промежуточного шага, однако мне сказали, что последние компиляторы («горячая точка») могут достичь этой скорости или даже превзойти ее.

Возможно, это скорее вопрос компилятора, чем вопрос языка, но может ли кто-нибудь объяснить на простом английском, как может один из этих языков виртуальных машин работать лучше, чем родной язык?


Java и C # могут выполнять оптимизацию на основе того, как приложение фактически запускается с использованием кода, доступного во время выполнения. например, он может встроить код в разделяемую библиотеку, который может фактически изменяться во время работы программы и при этом оставаться правильным.
Питер Лоури

Некоторые фактические измерения для проверки перед чтением много очень слоеного теории в этих ответах: shootout.alioth.debian.org/u32/...
Justicle

Ответы:


178

Как правило, C # и Java могут быть такими же быстрыми или быстрее, потому что JIT-компилятор - компилятор, который компилирует ваш IL при первом запуске - может выполнять оптимизацию, которую скомпилированная программа C ++ не может сделать, потому что она может запрашивать машину. Он может определить, является ли машина Intel или AMD; Pentium 4, Core Solo или Core Duo; или если поддерживает SSE4 и т. д.

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

Кроме того, некоторые языковые функции позволяют компилятору C # и Java делать предположения о вашем коде, что позволяет ему оптимизировать определенные части, которые просто небезопасны для компилятора C / C ++. Когда у вас есть доступ к указателям, существует множество оптимизаций, которые просто небезопасны.

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

Теперь я не могу говорить от имени Java по этому следующему пункту, но я знаю, что, например, C # фактически удалит методы и вызовы методов, когда узнает, что тело метода пусто. И он будет использовать такую ​​логику во всем вашем коде.

Итак, как видите, есть множество причин, по которым определенные реализации C # или Java будут быстрее.

Теперь, когда все сказано, в C ++ можно сделать определенные оптимизации, которые сведут на нет все, что вы могли бы сделать с C #, особенно в области графики и в любое время, когда вы находитесь рядом с оборудованием. Указатели здесь творят чудеса.

Так что, в зависимости от того, что вы пишете, я бы выбрал то или иное. Но если вы пишете что-то, что не зависит от оборудования (драйвер, видеоигра и т. Д.), Я бы не стал беспокоиться о производительности C # (опять же, не могу говорить о Java). Это будет нормально.

Со стороны Java, @Swati указывает на хорошую статью:

https://www.ibm.com/developerworks/library/j-jtp09275


Ваши аргументы неверны - программы на C ++ создаются для своей целевой архитектуры, им не нужно переключаться во время выполнения.
Justicle

3
@Justicle Лучшее, что ваш компилятор C ++ может предложить для разных архитектур, - это обычно x86, x64, ARM и еще много чего. Теперь вы можете указать ему использовать определенные функции (скажем, SSE2), и, если вам повезет, он даже сгенерирует некоторый резервный код, если эта функция недоступна, но это настолько детально, насколько это возможно. Определенно никакой специализации в зависимости от размеров кеша и прочего.
Voo

4
См. Shotout.alioth.debian.org/u32/… примеры того, что эта теория не реализуется.
Justicle

1
Если честно, это один из худших ответов. Это настолько необоснованно, что я мог бы просто перевернуть это. Слишком много обобщений, слишком много незнаний (оптимизация пустых функций - это на самом деле только верхушка айсберга). У компиляторов C ++ есть одна роскошь: Время. Еще одна роскошь: проверка не проводится. Но вы можете найти больше на stackoverflow.com/questions/145110/c-performance-vs-java-c/… .
Себастьян Мах

1
@OrionAdrian хорошо мы полный круг теперь ... См shootout.alioth.debian.org/u32/... примеры этой теории не происходит. Другими словами, показывают нам , что ваша теория может быть доказана правильно , прежде чем принимать неопределенные спекулятивные заявления.
Justicle

197

JIT против статического компилятора

Как уже было сказано в предыдущих сообщениях, JIT может компилировать IL / байт-код в собственный код во время выполнения. Стоимость этого была названа, но не до конца:

У JIT есть одна серьезная проблема: он не может скомпилировать все: JIT-компиляция требует времени, поэтому JIT будет компилировать только некоторые части кода, тогда как статический компилятор создаст полный собственный двоичный код: для некоторых программ статический компилятор просто легко превзойдет JIT.

Конечно, C # (или Java, или VB) обычно быстрее создает жизнеспособное и надежное решение, чем C ++ (хотя бы потому, что C ++ имеет сложную семантику, а стандартная библиотека C ++, хотя и интересна и мощна, но довольно плохая по сравнению с полной версией. объем стандартной библиотеки из .NET или Java), поэтому обычно разница между C ++ и .NET или Java JIT не будет видна большинству пользователей, а для тех двоичных файлов, которые имеют решающее значение, вы все равно можете вызвать обработку C ++ из C # или Java (даже если такие нативные вызовы могут быть довольно дорогостоящими сами по себе) ...

C ++ метапрограммирование

Обратите внимание, что обычно вы сравниваете код времени выполнения C ++ с его эквивалентом на C # или Java. Но у C ++ есть одна особенность, которая может превзойти Java / C # из коробки, а именно метапрограммирование шаблонов: обработка кода будет выполняться во время компиляции (что значительно увеличивает время компиляции), что приводит к нулевому (или почти нулевому) времени выполнения.

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

Изменить 2011-06-10: В C ++ игра с типами выполняется во время компиляции, что означает создание универсального кода, который вызывает неуниверсальный код (например, общий синтаксический анализатор от строки до типа T, вызов стандартной библиотеки API для типов T, которые он распознает, и сделать синтаксический анализатор легко расширяемым пользователем) очень просто и очень эффективно, тогда как эквивалент в Java или C # в лучшем случае болезненно писать, и он всегда будет медленнее и разрешается во время выполнения, даже если типы известны во время компиляции, это означает, что ваша единственная надежда - на то, что JIT все это встроит.

...

Изменить 2011-09-20: Команда, стоящая за Blitz ++ ( домашняя страница , Википедия ), пошла по этому пути, и, по-видимому, их цель - достичь производительности FORTRAN в научных расчетах, максимально перемещаясь от выполнения во время выполнения ко времени компиляции с помощью метапрограммирования шаблонов C ++ , Таким образом, часть « Я еще не видел реальный жизненный эффект на это », о котором я писал выше, очевидно, действительно существует в реальной жизни.

Собственное использование памяти C ++

C ++ использует память, отличную от Java / C #, и, следовательно, имеет другие преимущества / недостатки.

Независимо от JIT-оптимизации, ничто не пойдет так быстро, как прямой указатель доступа к памяти (давайте на минутку проигнорируем кеши процессора и т. Д.). Таким образом, если у вас есть непрерывные данные в памяти, доступ к ним через указатели C ++ (то есть указатели C ... Давайте отдадим должное Caesar) будет происходить в разы быстрее, чем в Java / C #. А в C ++ есть RAII, что значительно упрощает обработку, чем в C # или даже в Java. С ++ не нужно usingограничивать существование своих объектов. А в C ++ нет finallyпункта. Это не ошибка.

:-)

И, несмотря на примитивные структуры C #, объекты C ++ "в стеке" ничего не стоят при распределении и уничтожении, и им не потребуется сборщик мусора для работы в независимом потоке для очистки.

Что касается фрагментации памяти, распределители памяти в 2008 году - это не старые распределители памяти 1980 года, которые обычно сравнивают с GC: распределение C ++ не может быть перемещено в память, правда, но тогда, как в файловой системе Linux: кому нужен жесткий диск дефрагментация, когда фрагментации не происходит? Использование правильного распределителя для правильной задачи должно быть частью набора инструментов разработчика C ++. Теперь написать распределители памяти непросто, и тогда у большинства из нас есть дела поважнее, и для большинства случаев использования RAII или GC более чем достаточно.

Изменить 2011-10-04: Примеры эффективных распределителей: на платформах Windows, начиная с Vista, куча с низкой фрагментацией включена по умолчанию. Для предыдущих версий LFH можно было активировать, вызвав функцию WinAPI HeapSetInformation. ). В других ОС предусмотрены альтернативные распределители (см.https://secure.wikimedia.org/wikipedia/en/wiki/Malloc для списка)

Теперь модель памяти несколько усложняется с появлением многоядерных и многопоточных технологий. В этой области, я полагаю, .NET имеет преимущество, а Java, как мне сказали, занимает верхнюю позицию. Некоторому хакеру, работающему на «голом железе», легко похвалить его код «около машины». Но сейчас гораздо труднее произвести лучшую сборку вручную, чем позволить компилятору выполнять свою работу. Для C ++ компилятор обычно становился лучше, чем хакерский за последние десять лет. Для C # и Java это еще проще.

Тем не менее, новый стандарт C ++ 0x наложит простую модель памяти на компиляторы C ++, которая стандартизирует (и, таким образом, упростит) эффективный код многопроцессорной / параллельной / многопоточной обработки в C ++ и сделает оптимизацию более простой и безопасной для компиляторов. Но потом, через пару лет мы увидим, будут ли выполнены его обещания.

C ++ / CLI против C # / VB.NET

Примечание. В этом разделе я говорю о C ++ / CLI, то есть о C ++, размещенном в .NET, а не о собственном C ++.

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

Тот же самый код, скомпилированный на C ++ / CLI (или его предке, Managed C ++), может быть в разы быстрее, чем тот же код, созданный на C # (или VB.NET, компилятор которого создает тот же IL, что и C #).

Потому что статический компилятор C ++ был намного лучше для создания уже оптимизированного кода, чем C #.

Например, встраивание функций в .NET ограничено функциями, длина байт-кода которых меньше или равна 32 байтам. Итак, некоторый код на C # создаст 40-байтовый метод доступа, который никогда не будет встроен JIT. Тот же код в C ++ / CLI создаст 20-байтовый метод доступа, который будет встроен JIT.

Другой пример - временные переменные, которые просто компилируются компилятором C ++, но при этом упоминаются в IL, созданном компилятором C #. Оптимизация статической компиляции C ++ приведет к меньшему количеству кода, таким образом, снова разрешит более агрессивную JIT-оптимизацию.

Причиной этого было предположение, что компилятор C ++ / CLI извлек выгоду из обширных методов оптимизации собственного компилятора C ++.

Вывод

Я люблю C ++.

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

Изменить (2011-06-06)

Мой опыт работы с C # /. NET

Сейчас у меня 5 месяцев почти эксклюзивного профессионального программирования на C # (что дополняет мое резюме, уже полное C ++ и Java, а также немного C ++ / CLI).

Я играл с WinForms (кхм ...) и WCF (круто!) И WPF (круто !!!! Как через XAML, так и через необработанный C #. WPF настолько прост, я считаю, что Swing просто не может сравниться с ним) и C # 4.0.

Вывод таков: хотя легче / быстрее создать код, работающий на C # / Java, чем на C ++, гораздо сложнее создать надежный, безопасный и надежный код на C # (и даже сложнее на Java), чем на C ++. Причин предостаточно, но их можно резюмировать следующим образом:

  1. Обобщения не так эффективны, как шаблоны ( попробуйте написать эффективный универсальный метод Parse (от строки до T) или эффективный эквивалент boost :: lexical_cast в C #, чтобы понять проблему )
  2. RAII остается непревзойденным ( GC все еще может протекать (да, мне пришлось справиться с этой проблемой) и будет обрабатывать только память. Даже C # usingне такой простой и мощный, потому что писать правильные реализации Dispose сложно )
  3. C # readonlyи Java finalнигде не так полезны, как C ++const ( вы не можете раскрыть сложные данные только для чтения (например, дерево узлов) в C # без огромной работы, хотя это встроенная функция C ++. Неизменяемые данные - интересное решение , но не все можно сделать неизменяемым, поэтому этого явно недостаточно ).

Итак, C # остается приятным языком, пока вы хотите что-то, что работает, но разочаровывающим языком в тот момент, когда вам нужно что-то, что всегда и безопасно работает.

Java вызывает еще большее разочарование, поскольку имеет те же проблемы, что и C #, и даже больше: из-за отсутствия эквивалента usingключевого слова C # мой очень опытный коллега потратил слишком много времени, чтобы убедиться, что его ресурсы правильно освобождены, тогда как эквивалент в C ++ имел бы было легко (с использованием деструкторов и умных указателей).

Поэтому я полагаю, что прирост производительности C # / Java заметен для большей части кода ... до того дня, когда вам понадобится сделать код как можно более совершенным. В тот день ты познаешь боль. (вы не поверите, что спрашивают наши серверы и приложения с графическим интерфейсом ...).

О серверной Java и C ++

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

В последние годы была тенденция к тому, чтобы серверные приложения Java были предназначены для замены старых серверных приложений C ++, поскольку Java имеет множество фреймворков / инструментов, проста в обслуживании, развертывании и т. Д. И т. Д.

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

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

Вывод

Нет ничего проще, чем ожидалось.

Java и даже C # - отличные языки с обширными стандартными библиотеками и фреймворками, где вы можете быстро кодировать и очень скоро получить результат.

Но когда вам нужна чистая мощность, мощная и систематическая оптимизация, сильная поддержка компилятора, мощные языковые функции и абсолютная безопасность, Java и C # затрудняют достижение последнего недостающего, но критического процента качества, вам нужно оставаться выше конкурентов.

Как будто вам нужно меньше времени и менее опытных разработчиков на C # / Java, чем на C ++, чтобы создать код среднего качества, но, с другой стороны, в тот момент, когда вам нужно было отличное или идеальное качество кода, стало внезапно легче и быстрее получить результаты. прямо в C ++.

Конечно, это мое собственное восприятие, возможно, ограниченное нашими конкретными потребностями.

Но, тем не менее, это то, что происходит сегодня, как в командах GUI, так и в командах на стороне сервера.

Конечно, я обновлю этот пост, если произойдет что-то новое.

Изменить (2011-06-22)

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

[...] Версия Java была, вероятно, самой простой в реализации, но трудной для анализа производительности. В частности, эффекты, связанные со сборкой мусора, были сложными и очень трудными для настройки ».

Источники:

Изменить (2011-09-20)

«В Facebook ходят слухи, что« разумно написанный код C ++ просто работает быстро », что подчеркивает огромные усилия, затраченные на оптимизацию кода PHP и Java. Как это ни парадоксально, код на C ++ сложнее написать, чем на других языках, но эффективный код - это намного проще [писать на C ++, чем на других языках] ".

- Херб Саттер в // build / , цитирует Андрея Александреску.

Источники:


8
Вы редактируете через 5 месяцев C # точно описывает мой собственный опыт (шаблоны лучше, константы лучше, RAII). +1. Эти три остаются моими личными убийственными функциями для C ++ (или D, на что у меня пока не было времени).
Себастьян Мах

«Обработка кода будет производиться во время компиляции». Следовательно, метапрограммирование шаблонов работает только в программе, доступно во время компиляции, что часто бывает не так, например, невозможно написать конкурентоспособную библиотеку регулярных выражений на ванильном C ++, потому что она неспособна генерировать код во время выполнения (важный аспект метапрограммированием).
JD

«игра с типами выполняется во время компиляции ... эквивалент в Java или C # в лучшем случае болезненно писать, и всегда будет медленнее и разрешаться во время выполнения, даже если типы известны во время компиляции». В C # это верно только для ссылочных типов и неверно для типов значений.
JD

1
«Независимо от JIT-оптимизации, ничто не пойдет так быстро, как прямой указатель доступа к памяти ... если у вас есть непрерывные данные в памяти, доступ к ним через указатели C ++ (то есть указатели C ... давайте отдадим должное Цезарю) быстрее, чем в Java / C # ». Люди наблюдали, как Java превосходит C ++ в тесте SOR из теста SciMark2 именно потому, что указатели препятствуют оптимизации, связанной с алиасингом. blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD

Также стоит отметить, что .NET выполняет специализацию типов универсальных шаблонов в динамически связанных библиотеках после связывания, тогда как C ++ не может этого сделать, поскольку шаблоны должны быть разрешены перед связыванием. И очевидно, что большое преимущество дженериков перед шаблонами - это понятные сообщения об ошибках.
JD

48

Всякий раз, когда я говорю об управляемой и неуправляемой производительности, мне нравится указывать на серию статей, в которых Рико (и Раймонд) сравнивали версии китайско-английского словаря на C ++ и C #. Этот поиск в Google позволит вам прочитать сами, но мне нравится резюме Рико.

Так мне стыдно за свое сокрушительное поражение? Едва. Управляемый код без каких-либо усилий получил очень хороший результат. Чтобы победить управляемого Раймонда пришлось:

  • Написать свой собственный файловый ввод / вывод
  • Напишите свой собственный строковый класс
  • Напишите свой собственный распределитель
  • Напишите свое собственное международное картографирование

Конечно, для этого он использовал доступные библиотеки нижнего уровня, но это еще большая работа. Можете ли вы назвать то, что осталось, программой на STL? Я так не думаю, я думаю, что он сохранил класс std :: vector, который в конечном итоге никогда не был проблемой, и он сохранил функцию поиска. Практически все остальное ушло.

Итак, да, вы определенно можете превзойти CLR. Думаю, Раймонд может сделать свою программу еще быстрее.

Интересно, что время синтаксического анализа файла, сообщаемое внутренними таймерами обеих программ, примерно одинаково - 30 мсек для каждого. Разница в накладных расходах.

Для меня суть в том, что неуправляемой версии потребовалось 6 ревизий, чтобы превзойти управляемую версию, которая была простым портом исходного неуправляемого кода. Если вам нужен каждый последний бит производительности (и у вас есть время и опыт, чтобы получить его), вам придется отказаться от управления, но для меня я воспользуюсь преимуществом на порядок, которое у меня есть в первых версиях над 33 % Я получаю, если попробую 6 раз.


3
ссылка мертва, здесь найдена упомянутая статья: blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp

Прежде всего, если мы посмотрим на код Раймонда Чена, он явно не очень хорошо понимает C ++ или структуры данных. Его код почти достигает низкоуровневого кода C даже в тех случаях, когда код C не имеет преимуществ в производительности (это просто похоже на недоверие и, возможно, отсутствие знаний о том, как использовать профилировщики). Он также не смог понять наиболее алгоритмически обоснованный способ реализации словаря (ради Христа он использовал std :: find). Если есть что-то хорошее в Java, Python, C # и т. Д. - все они предоставляют очень эффективные словари ...
stinky472,

Попытки или даже std :: map будут намного лучше по сравнению с C ++ или даже хеш-таблицей. Наконец, словарь - это именно тот тип программ, который больше всего выигрывает от высокоуровневых библиотек и фреймворков. Он не столько демонстрирует различия в языке, сколько в задействованных библиотеках (из которых я бы с радостью сказал, что C # гораздо более полный и предоставляет гораздо больше инструментов, подходящих для этой задачи). Покажите для сравнения программу, которая манипулирует большими блоками памяти, например, крупномасштабный матричный / векторный код. Это
решит эту проблему

26

Компиляция для конкретных оптимизаций ЦП обычно переоценивается. Просто возьмите программу на C ++, скомпилируйте с оптимизацией для Pentium PRO и запустите на Pentium 4. Затем перекомпилируйте с optimize для Pentium 4. Я провел долгие дни, делая это с несколькими программами. Общие результаты ?? Обычно производительность увеличивается менее чем на 2-3%. Таким образом, теоретических преимуществ JIT практически нет. Большинство различий в производительности можно наблюдать только при использовании функций обработки скалярных данных, что в конечном итоге потребует точной настройки вручную для достижения максимальной производительности. Оптимизация такого рода медленная и дорогостоящая, что делает ее иногда непригодной для JIT.

В реальном мире и реальных приложениях C ++ по-прежнему обычно быстрее, чем java, в основном из-за меньшего объема памяти, что приводит к лучшей производительности кеша.

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


4
Вы компилируете не столько для оптимизации ЦП, сколько для оптимизации пути во время выполнения. Если вы обнаружите, что метод очень часто вызывается с определенным параметром, вы можете предварительно скомпилировать эту подпрограмму с этим параметром в качестве константы, которая может (в случае логического, управляющего потоком) исключить гигантские куски работы. C ++ не может приблизиться к такой оптимизации.
Bill K,

1
Так как же JIT перекомпилируют подпрограммы, чтобы воспользоваться наблюдаемыми путями выполнения, и насколько это важно?
Дэвид Торнли,

2
@Bill Я могу смешивать две вещи ... но разве предсказание ветвления, выполняемое во время выполнения в конвейере инструкций, не достигает аналогичных целей независимо от языка?
Харди

@ Hardy да, ЦП может предсказывать ветвления независимо от языка, но он не может исключить весь цикл, наблюдая, что цикл ни на что не влияет. Он также не заметит, что mult (0) жестко привязан к возврату 0 и просто заменит весь вызов метода на if (param == 0) result = 0; и избегайте всего вызова функции / метода. C мог бы делать это, если бы компилятор имел исчерпывающий обзор того, что происходило, но обычно во время компиляции у него не было достаточно информации.
Bill K

21

JIT (Just In Time Compiling) может быть невероятно быстрым, потому что он оптимизируется для целевой платформы.

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

Основная концепция .NET JIT работает следующим образом (сильно упрощенная):

Первый вызов метода:

  • Ваш программный код вызывает метод Foo ()
  • CLR смотрит на тип, реализующий Foo (), и получает связанные с ним метаданные.
  • Из метаданных CLR знает, в каком адресе памяти хранится промежуточный байт-код (IL).
  • CLR выделяет блок памяти и вызывает JIT.
  • JIT компилирует IL в машинный код, помещает его в выделенную память, а затем изменяет указатель функции в метаданных типа Foo (), чтобы он указывал на этот машинный код.
  • Собственный код запущен.

Вызов метода второй раз:

  • Ваш программный код вызывает метод Foo ()
  • CLR смотрит на тип, реализующий Foo (), и находит указатель на функцию в метаданных.
  • Собственный код запускается в этом месте памяти.

Как видите, во второй раз это практически тот же процесс, что и в C ++, за исключением оптимизации в реальном времени.

Тем не менее, есть и другие накладные расходы, замедляющие управляемый язык, но JIT очень помогает.


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

12

Мне нравится орион адриан ответ , но есть еще один аспект.

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

Да, программа на C ++ способна быть быстрее, чем C #, по любому заданному (нетривиальному?) Алгоритму, но программа на C # часто будет так же быстро или быстрее, чем «наивная» реализация на C ++ и оптимизированная версия на C ++. на разработку уйдет больше времени, и он все равно может превзойти версию C # с очень небольшим отрывом. Так действительно ли оно того стоит?

Вам придется отвечать на этот вопрос один за другим.

Тем не менее, я давний поклонник C ++ и считаю его невероятно выразительным и мощным языком, который иногда недооценивают. Но во многих «реальных» проблемах (лично для меня это означает «те, за решение которых мне платят») C # выполнит свою работу быстрее и безопаснее.

Самый большой штраф, который вы платите? Многие программы .NET и Java занимают много памяти. Я видел, как приложения .NET и Java занимают «сотни» мегабайт памяти, в то время как программы на C ++ аналогичной сложности едва занимают «десятки» мегабайт.


7

Я не уверен, как часто вы обнаружите, что код Java будет работать быстрее, чем C ++, даже с Hotspot, но я попытаюсь объяснить, как это могло произойти.

Считайте скомпилированный код Java интерпретируемым машинным языком для JVM. Когда процессор Hotspot замечает, что определенные части скомпилированного кода будут использоваться много раз, он выполняет оптимизацию машинного кода. Поскольку ручная настройка Assembly почти всегда быстрее, чем код, скомпилированный на C ++, можно предположить, что программно настроенный машинный код тоже не будет плохим.

Итак, для очень повторяющегося кода я мог видеть, где JVM Hotspot сможет запускать Java быстрее, чем C ++ ... до тех пор, пока не вступит в игру сборка мусора. :)


Не могли бы вы развить утверждение Since hand-tuning Assembly is almost always faster than C++ compiled code? Что вы имеете в виду под "настраиваемой вручную сборкой" и "кодом, скомпилированным на C ++"?
paercebal

Что ж, он основан на идее, что оптимизатор компилятора следует правилам, а кодеры - нет. Таким образом, всегда будет код, который, по мнению оптимизатора, он не может оптимизировать идеально, в то время как человек мог бы, глядя на более широкую картину или больше зная о том, что код действительно делает. Я добавлю, что это комментарий трехлетней давности, и я знаю о HotSpot больше, чем раньше, и я легко вижу, что динамическая оптимизация - ОЧЕНЬ хороший способ ускорить выполнение кода.
billjamesdev

1. Оптимизация из Hotspot или любой другой JIT по-прежнему является оптимизацией компилятора. JIT имеет преимущество перед статическим компилятором в том, что он может встроить некоторые результаты (часто вызываемый код) или даже выполнять оптимизацию на основе выполняемого процессора, но это все же оптимизация компилятора. , , 2. Я думаю, вы говорите об оптимизации алгоритма, а не о «тонкой настройке сборки». "точная настройка сборки вручную человеком-кодером" не давала лучших результатов, чем оптимизация компилятора более десяти лет. Фактически, человек, играющий со сборкой, обычно отворачивается от любой оптимизации ...
paercebal

Хорошо, я понимаю, что использую неправильную терминологию: «оптимизация компилятора», а не «статическая оптимизация». Я хотел бы отметить, что, по крайней мере, в игровой индустрии, совсем недавно, что касается PS2, мы все еще использовали ручную сборку в местах для «оптимизации» для конкретных чипов, которые, как мы знали, были на консоли; кросс-компиляторы для этих новых микросхем еще не столь сложны, как для архитектур x86. Вернемся к исходному вопросу выше: преимущество JIT заключается в возможности измерения до оптимизации, что является хорошей вещью (TM)
billjamesdev

Обратите внимание, что большинство производственных сборщиков мусора также используют рукописный ассемблер, потому что C / C ++ его не сокращает.
JD

6

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

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

Кроме того, C ++ догоняет «новые» (обратите внимание на кавычки) функции, такие как контейнеры STL, автоматические указатели и т. Д. - см., Например, библиотеку boost. И иногда вы можете обнаружить, что самый быстрый способ выполнить некоторую задачу требует такой техники, как арифметика указателей, которая запрещена в языках более высокого уровня, хотя обычно они позволяют вам обращаться к библиотеке, написанной на языке, который может реализовать ее по желанию. ,

Главное - знать, какой язык вы используете, связанный с ним API, что он может делать и каковы его ограничения.


5

Я тоже не знаю ... мои программы на Java всегда медленные. :-) Я никогда особо не замечал, чтобы программы на C # были особенно медленными.


4

Вот еще один интересный тест, который вы можете попробовать на собственном компьютере.

Он сравнивает ASM, VC ++, C #, Silverlight, Java-апплет, Javascript, Flash (AS3).

Демонстрация скорости плагина Roozz

Обратите внимание, что скорость javascript сильно зависит от того, какой браузер его запускает. То же самое верно для Flash и Silverlight, потому что эти плагины работают в том же процессе, что и хост-браузер. Но плагин Roozz запускает стандартные файлы .exe, которые запускаются в собственном процессе, поэтому браузер хоста не влияет на скорость.


4

Вы должны определить «работать лучше, чем ..». Я знаю, вы спрашивали о скорости, но это еще не все.

  • Производят ли виртуальные машины больше накладных расходов во время выполнения? Да!
  • У них больше рабочей памяти кушают? Да!
  • У них более высокие затраты на запуск (инициализация среды выполнения и JIT-компилятор)? Да!
  • Они требуют установки огромной библиотеки? Да!

И так далее, предвзято, да;)

Используя C # и Java, вы платите цену за то, что получаете (более быстрое кодирование, автоматическое управление памятью, большая библиотека и т. Д.). Но по мелочам торговаться особо некуда: бери весь пакет или ничего.

Даже если эти языки могут оптимизировать некоторый код для выполнения быстрее, чем скомпилированный код, весь подход (IMHO) неэффективен. Представьте себе, что вы каждый день проезжаете 5 миль до рабочего места на грузовике! Это удобно, приятно, вы в безопасности (крайняя зона деформации), а после того, как вы нажмете на газ в течение некоторого времени, он будет даже таким же быстрым, как стандартный автомобиль! Почему нам всем не ехать на работу на грузовике? ;)

В C ++ вы получаете то, за что платите, не больше и не меньше.

Цитата Бьярна Страуструпа: «C ++ - мой любимый язык со сборкой мусора, потому что он генерирует так мало мусора» текст ссылки


Что ж, я думаю, что он хорошо понимает его недостатки, он также сказал: «С позволяет легко выстрелить себе в ногу; С ++ делает это сложнее, но когда вы это делаете, он отрывает вам всю ногу»;)
Фрунси

«Требуется ли им установленная огромная библиотека?» Я считаю, что Java решает эту проблему с помощью головоломки проекта.
toc777

«В C ++ вы получаете то, за что платите, не больше и не меньше». Пример счетчика: я тестировал реализацию дерева RB в OCaml и C ++ (GNU GCC), в которой использовалось исключение для длительного выхода из рекурсии, если добавляемый элемент уже присутствовал, чтобы повторно использовать существующий набор. OCaml был до 6 раз быстрее, чем C ++, потому что он не платит за проверку деструкторов при раскручивании стека.
JD

3
@Jon: но в какой-то (позже?) Момент времени он все равно должен уничтожить объекты (по крайней мере, он должен освободить свою память). Также обратите внимание, что исключения предназначены для исключительных случаев, по крайней мере, в C ++ это правило следует соблюдать. Исключения C ++ могут быть тяжелыми, когда возникают исключения, это компромисс.
Frunsi

@Jon: может быть, попробуйте повторить тест timesна оболочке. Так что он проверяет всю программу, а не только один аспект. Тогда похожи ли результаты?
Frunsi

3

Исполняемый код, созданный компилятором Java или C #, не интерпретируется - он компилируется в собственный код «точно в срок» (JIT). Итак, первый раз, когда код в программе Java / C # встречается во время выполнения, возникают некоторые накладные расходы, так как «компилятор времени выполнения» (он же JIT-компилятор) превращает байтовый код (Java) или код IL (C #) в машинные инструкции машинного кода. Однако в следующий раз, когда этот код будет встречен во время работы приложения, собственный код будет выполнен немедленно. Это объясняет, почему некоторые программы Java / C # сначала кажутся медленными, но затем работают лучше, чем дольше они работают. Хорошим примером является веб-сайт ASP.Net. Самый первый доступ к веб-сайту может быть немного медленнее, так как код C # компилируется в собственный код JIT-компилятором.


3

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

Имейте в виду, что восприятие вашим пользователем скорости написанного вами программного обеспечения зависит от многих других факторов, помимо того, насколько хорошо оптимизирует кодогенератор. Вот некоторые примеры:

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

  • Мой C # легче читать и понимать, чем мой C ++. У меня также есть другие способы убедить себя, что мой код C # работает правильно. Это означает, что я могу оптимизировать свои алгоритмы с меньшим риском появления ошибок (а пользователям не нравится программное обеспечение, которое дает сбой, даже если оно делает это быстро!)

  • Я могу создавать свои программы на C # быстрее, чем на C ++. Это освобождает время для работы над производительностью и по-прежнему доставляет мое программное обеспечение вовремя.

  • Легче написать хороший пользовательский интерфейс на C #, чем на C ++, поэтому я с большей вероятностью смогу отодвинуть работу в фоновый режим, пока пользовательский интерфейс остается отзывчивым, или обеспечить прогресс или импульсный интерфейс, когда программе придется на некоторое время заблокироваться. Это не делает ничего быстрее, но делает пользователей более счастливыми в ожидании.

Все, что я сказал о C #, вероятно, верно и для Java, у меня просто нет опыта, чтобы сказать наверняка.


3

Если вы программист на Java / C # и изучаете C ++, у вас возникнет соблазн продолжать думать в терминах Java / C # и дословно переводить синтаксис C ++. В этом случае вы получаете только упомянутые ранее преимущества нативного кода по сравнению с интерпретируемым / JIT. Чтобы получить наибольший прирост производительности в C ++ по сравнению с Java / C #, вам нужно научиться думать на C ++ и разрабатывать код специально для использования сильных сторон C ++.

Перефразируя Эдсгера Дейкстру : [ваш родной язык] калечит разум безвозвратно.
Перефразируя Джеффа Этвуда : вы можете писать [ваш первый язык] на любом новом языке.


1
Я подозреваю, что поговорка «Вы можете писать FORTRAN на любом языке» появилась еще до карьеры Джеффа.
Дэвид Торнли,

3

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

Стандартное выделение памяти в Java / C # также происходит быстрее, а освобождение памяти (GC) не намного медленнее, но менее детерминировано.


Обратите внимание, что freeи deleteтакже не являются детерминированными, и GC можно сделать детерминированным, не выделяя.
JD

3

Языки виртуальных машин вряд ли превзойдут по производительности скомпилированные языки, но они могут подойти настолько близко, что это не имеет значения, по (по крайней мере) следующим причинам (здесь я говорю от имени Java, поскольку я никогда не работал с C #).

1 / Среда выполнения Java обычно способна обнаруживать части кода, которые часто запускаются, и выполнять JIT-компиляцию этих разделов, чтобы в будущем они выполнялись с полной скоростью компиляции.

2 / Обширные части библиотек Java скомпилированы так, что, когда вы вызываете библиотечную функцию, вы выполняете скомпилированный код, а не интерпретируете. Вы можете увидеть код (на C), загрузив OpenJDK.

3 / Если вы не выполняете массовые вычисления, большую часть времени ваша программа ждет ввода от очень медленного (условно говоря) человека.

4 / Поскольку большая часть проверки байт-кода Java выполняется во время загрузки класса, обычные накладные расходы на проверки времени выполнения значительно сокращаются.

5 / В худшем случае требовательный к производительности код можно извлечь в скомпилированный модуль и вызвать из Java (см. JNI), чтобы он работал на полной скорости.

Таким образом, байт-код Java никогда не превзойдет собственный машинный язык, но есть способы смягчить это. Большим преимуществом Java (как мне кажется) является ОГРОМНАЯ стандартная библиотека и кроссплатформенность.


1
По пункту 2, «2 / Обширные части библиотек Java скомпилированы так, что при вызове библиотечной функции вы выполняете скомпилированный код, а не интерпретируете»: есть ли у вас ссылка на это? Если бы все было действительно так, как вы описываете, я бы ожидал, что мой отладчик будет часто сталкиваться с машинным кодом, но я этого не делаю.
cero

Re: cero Отладчики часто используют менее эффективные, но более выразительные пути, и поэтому не являются хорошим маркером для чего-либо, связанного с производительностью.
Guvante 07

2
Эта HUGH-библиотека дает еще один огромный прирост производительности - библиотечный код, вероятно, лучше написан, чем то, что многие программисты будут писать самостоятельно (учитывая ограниченное время и отсутствие специальных знаний) и на Java, по многим причинам программисты часто используют библиотека.
Liran Orevi 03

3

Орион Адриан , позвольте мне перевернуть ваш пост, чтобы увидеть, насколько ваши замечания необоснованны, потому что о C ++ можно много сказать. И сообщение о том, что компилятор Java / C # оптимизирует пустые функции, действительно заставляет вас звучать так, как будто вы не мой эксперт по оптимизации, потому что а) почему настоящая программа должна содержать пустые функции, за исключением действительно плохого устаревшего кода, б) это действительно не черная и передовая оптимизация.

Помимо этой фразы, вы открыто говорили об указателях, но разве объекты в Java и C # в основном не работают как указатели C ++? Могут ли они не перекрываться? Разве они не могут быть нулевыми? C (и большинство реализаций C ++) имеет ключевое слово restrict, оба имеют типы значений, C ++ имеет ссылку на значение с ненулевой гарантией. Что предлагают Java и C #?

>>>>>>>>>>

Как правило, C и C ++ могут быть такими же быстрыми или более быстрыми, потому что компилятор AOT - компилятор, который раз и навсегда компилирует ваш код перед развертыванием на вашем многоядерном сервере сборки с большой памятью, - может выполнять оптимизацию, которую скомпилированная программа C # не может, потому что у него на это куча времени. Компилятор может определить, является ли машина Intel или AMD; Pentium 4, Core Solo или Core Duo; или если поддерживает SSE4 и т. д., и если ваш компилятор не поддерживает диспетчеризацию во время выполнения, вы можете решить эту проблему самостоятельно, развернув несколько специализированных двоичных файлов.

Программа AC # обычно компилируется после ее запуска, так что она неплохо работает на всех машинах, но не оптимизирована так сильно, как могла бы быть для одной конфигурации (например, процессора, набора команд, другого оборудования), и на нее нужно потратить некоторое время первый. Такие функции, как деление цикла, инверсия цикла, автоматическая векторизация, оптимизация всей программы, расширение шаблона, IPO и многие другие, очень трудно решить полностью и полностью таким образом, чтобы не раздражать конечного пользователя.

Кроме того, некоторые языковые функции позволяют компилятору C ++ или C делать предположения о вашем коде, что позволяет ему оптимизировать определенные части, которые просто небезопасны для компилятора Java / C #. Когда у вас нет доступа к полному идентификатору типа универсальных шаблонов или гарантированному потоку программы, существует множество оптимизаций, которые просто небезопасны.

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

Теперь я не могу говорить о Java по этому следующему пункту, но я знаю, что компиляторы C ++, например, фактически удаляют методы и вызовы методов, когда он знает, что тело метода пусто, он устраняет общие подвыражения, он может попытаться повторить попытку чтобы найти оптимальное использование регистров, он не требует проверки границ, он будет автовекторизовать циклы и внутренние циклы и будет инвертировать внутренний во внешний, он перемещает условные выражения из циклов, он разбивает и не разделяет циклы. Он расширит std :: vector в собственные массивы с нулевыми накладными расходами, как если бы вы делали способ C. Он будет выполнять межпроцедурные оптимизации. Он будет создавать возвращаемые значения непосредственно на сайте вызывающего абонента. Он будет складывать и распространять выражения. Он будет переупорядочивать данные в удобном для кеша виде. Он будет выполнять скачкообразную заправку. Он позволяет писать трассировщики лучей во время компиляции с нулевыми издержками времени выполнения. Это сделает очень дорогостоящие оптимизации на основе графов. Это приведет к снижению прочности, если он заменит определенные коды синтаксически совершенно неравным, но семантически эквивалентным кодом (старый «xor foo, foo» - это всего лишь простейшая, хотя и устаревшая оптимизация такого рода). Если вы любезно спросите, вы можете опустить стандарты IEEE с плавающей запятой и включить еще больше оптимизаций, таких как изменение порядка операндов с плавающей запятой. После того, как он обработал и уничтожил ваш код, он может повторить весь процесс, потому что часто определенные оптимизации закладывают основу для еще более надежных оптимизаций. Он также может просто повторить попытку с перемешанными параметрами и посмотреть, как другой вариант оценивается во внутреннем рейтинге. И он будет использовать такую ​​логику во всем вашем коде. если он заменяет определенные коды синтаксически совершенно неравным, но семантически эквивалентным кодом (старый «xor foo, foo» - это всего лишь простейшая, хотя и устаревшая оптимизация такого рода). Если вы любезно спросите, вы можете опустить стандарты IEEE с плавающей запятой и включить еще больше оптимизаций, таких как изменение порядка операндов с плавающей запятой. После того, как он обработал и уничтожил ваш код, он может повторить весь процесс, потому что часто определенные оптимизации закладывают основу для еще более надежных оптимизаций. Он также может просто повторить попытку с перемешанными параметрами и посмотреть, как другой вариант оценивается во внутреннем рейтинге. И он будет использовать такую ​​логику во всем вашем коде. если он заменяет определенные коды синтаксически совершенно неравным, но семантически эквивалентным кодом (старый «xor foo, foo» - это всего лишь простейшая, хотя и устаревшая оптимизация такого рода). Если вы любезно спросите, вы можете опустить стандарты IEEE с плавающей запятой и включить еще больше оптимизаций, таких как изменение порядка операндов с плавающей запятой. После того, как он обработал и уничтожил ваш код, он может повторить весь процесс, потому что часто определенные оптимизации закладывают основу для еще более надежных оптимизаций. Он также может просто повторить попытку с перемешанными параметрами и посмотреть, как другой вариант оценивается во внутреннем рейтинге. И он будет использовать такую ​​логику во всем вашем коде. Если вы любезно спросите, вы можете опустить стандарты IEEE с плавающей запятой и включить еще больше оптимизаций, таких как изменение порядка операндов с плавающей запятой. После того, как он обработал и уничтожил ваш код, он может повторить весь процесс, потому что часто определенные оптимизации закладывают основу для еще более надежных оптимизаций. Он также может просто повторить попытку с перемешанными параметрами и посмотреть, как другой вариант оценивается во внутреннем рейтинге. И он будет использовать такую ​​логику во всем вашем коде. Если вы любезно спросите, вы можете опустить стандарты IEEE с плавающей запятой и включить еще больше оптимизаций, таких как изменение порядка операндов с плавающей запятой. После того, как он обработал и уничтожил ваш код, он может повторить весь процесс, потому что часто определенные оптимизации закладывают основу для еще более надежных оптимизаций. Он также может просто повторить попытку с перемешанными параметрами и посмотреть, как другой вариант оценивается во внутреннем рейтинге. И он будет использовать такую ​​логику во всем вашем коде. Он также может просто повторить попытку с перемешанными параметрами и посмотреть, как другой вариант оценивается во внутреннем рейтинге. И он будет использовать такую ​​логику во всем вашем коде. Он также может просто повторить попытку с перемешанными параметрами и посмотреть, как другой вариант оценивается во внутреннем рейтинге. И он будет использовать такую ​​логику во всем вашем коде.

Итак, как видите, есть множество причин, по которым определенные реализации C ++ или C будут быстрее.

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

Так что, в зависимости от того, что вы пишете, я бы выбрал то или иное. Но если вы пишете что-то, что не зависит от оборудования (драйвер, видеоигра и т. Д.), Я бы не стал беспокоиться о производительности C # (опять же, не могу говорить о Java). Это будет нормально.

<<<<<<<<<<

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

В любом случае, чтобы помириться: AOT - это здорово, как и JIT . Единственно правильный ответ может быть: это зависит от обстоятельств. И настоящие умные люди знают, что вы в любом случае можете использовать лучшее из обоих миров.


2

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

Однако шансы на то, что это действительно произойдет, довольно низки - если, возможно, у Java нет очень хорошо написанной библиотеки, а у вас есть собственная плохо написанная библиотека C ++.


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

2

На самом деле C # не работает на виртуальной машине, как Java. IL компилируется в язык ассемблера, который является полностью собственным кодом и выполняется с той же скоростью, что и собственный код. Вы можете выполнить предварительную JIT-компиляцию для .NET-приложения, которая полностью устранит затраты на JIT, и тогда вы будете запускать полностью собственный код.

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

Тем не менее, одна из сильных сторон управляемого кода состоит в том, что он может быть полностью проверяемым, т.е. вы можете убедиться, что код никогда не будет обращаться к памяти других процессов или делать что-то нестандартное, прежде чем вы его выполните. У Microsoft есть исследовательский прототип полностью управляемой операционной системы, который неожиданно показал, что 100% управляемая среда на самом деле может работать значительно быстрее, чем любая современная операционная система, за счет использования этой проверки для отключения функций безопасности, которые больше не нужны управляемым программам. (в некоторых случаях мы говорим как 10x). У SE Radio есть отличный эпизод, рассказывающий об этом проекте.


1

В некоторых случаях управляемый код может быть быстрее, чем собственный код. Например, алгоритмы сборки мусора «отметка и очистка» позволяют средам, таким как JRE или CLR, освобождать большое количество недолговечных (обычно) объектов за один проход, при этом большинство объектов кучи C / C ++ освобождаются по очереди. время.

Из википедии :

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

Тем не менее, я написал много C # и много C ++, и я провел много тестов. По моему опыту, C ++ намного быстрее , чем C #, двумя способами: (1) если взять какой - то код , который вы написали в C #, портирование на C ++ нативный код , как правило , быстрее. Насколько быстрее? Что ж, он сильно различается, но нередко можно увидеть улучшение скорости на 100%. (2) В некоторых случаях сборка мусора может значительно замедлить работу управляемого приложения. .NET CLR ужасно справляется с большими кучами (скажем,> 2 ГБ) и может в конечном итоге проводить много времени в сборке мусора - даже в приложениях, которые имеют мало - или даже не имеют - объектов со средней продолжительностью жизни.

Конечно, в большинстве случаев, которые я описал, управляемые языки достаточно быстры, в значительной степени, и компромисс между обслуживанием и кодированием для дополнительной производительности C ++ просто не лучший.


1
Проблема в том, что для длительных процессов, таких как веб-сервер, ваша память со временем станет настолько фрагментированной (в программе, написанной на C ++), что вам придется реализовать что-то, что напоминает сборку мусора (или перезапускать время от времени, см. IIS ).
Тони БенБрахим

3
Я не наблюдал этого в больших программах Unix, которые должны работать вечно. Они, как правило, написаны на C, который еще хуже для управления памятью, чем C ++.
Дэвид Торнли,

Конечно, вопрос в том, сравниваем ли мы реализацию программы в управляемом и неуправляемом коде или теоретическую максимальную производительность языка. Очевидно, что неуправляемый код всегда может быть как минимум столь же быстрым, как и управляемый, поскольку в худшем случае вы можете просто написать неуправляемую программу, которая будет делать то же самое, что и управляемый код! Но большинство проблем с производительностью алгоритмические, а не микро. Кроме того, вы не оптимизируете управляемый и неуправляемый код одинаково, поэтому «C ++ в C #» обычно не работает.
kyoryu 04

2
В C / C ++ вы можете размещать краткосрочные объекты в стеке, и вы делаете это, когда это необходимо. В управляемом коде вы не можете , у вас нет выбора. Кроме того, в C / C ++ вы можете размещать списки объектов в смежных областях (новый Foo [100]), а в управляемом коде - нет. Итак, ваше сравнение неверно. Что ж, эта сила выбора ложится бременем на разработчиков, но таким образом они учатся познавать мир, в котором живут (память ...).
Frunsi 06

@frunsi: «в C / C ++ вы можете размещать списки объектов в смежных областях (новый Foo [100]), в управляемом коде вы не можете». Это неверно. Типы локальных значений выделяются стеком, и вы даже можете размещать их массивы в стеке в C #. Существуют даже производственные системы, написанные на C #, которые в устойчивом состоянии полностью не выделяют память.
JD


1

На самом деле JVM Sun HotSpot использует "смешанный режим" выполнения. Он интерпретирует байт-код метода до тех пор, пока не определит (обычно через какой-то счетчик), что конкретный блок кода (метод, цикл, блок try-catch и т. Д.) Будет выполняться много раз, а затем JIT компилирует его. Время, необходимое для JIT-компиляции метода, часто занимает больше времени, чем если бы метод должен был быть интерпретирован, если это редко запускаемый метод. Производительность обычно выше для «смешанного режима», потому что JVM не тратит время на JIT-код, который редко, если вообще запускается. C # и .NET этого не делают. .NET JIT использует все, что часто тратит время.


1

Прочтите о Dynamo от HP Labs , интерпретаторе для PA-8000, который работает на PA-8000 и часто запускает программы быстрее, чем изначально. Тогда это совсем не покажется удивительным!

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

Часто это сводится к:

  • у программ есть горячие точки, поэтому даже если вы медленнее выполняете 95% кода, который вам нужно запустить, вы все равно можете быть конкурентоспособными по производительности, если вы быстрее на горячих 5%

  • HLL знает о ваших намерениях больше, чем LLL, например C / C ++, и поэтому может генерировать более оптимизированный код (у OCaml даже больше, а на практике часто даже быстрее)

  • JIT-компилятор имеет много информации, которой нет у статического компилятора (например, фактические данные, которые у вас есть на этот раз)

  • JIT-компилятор может выполнять оптимизацию во время выполнения, которую традиционным компоновщикам на самом деле не разрешено делать (например, переупорядочивание веток, чтобы общий случай был плоским, или встраивание вызовов библиотеки)

В общем, C / C ++ - довольно плохие языки с точки зрения производительности: относительно мало информации о ваших типах данных, нет информации о ваших данных и нет динамической среды выполнения, которая позволяла бы многое в плане оптимизации времени выполнения.


1

Вы можете получить короткие всплески, когда Java или CLR быстрее, чем C ++, но в целом производительность будет хуже для всего срока службы приложения: см. Www.codeproject.com/KB/dotnet/RuntimePerformance.aspx для некоторых результатов для этого.



1

Насколько я понимаю, C / C ++ создает собственный код для работы на конкретной архитектуре компьютера. И наоборот, такие языки, как Java и C #, работают поверх виртуальной машины, которая абстрагируется от собственной архитектуры. Логически казалось бы невозможным для Java или C # соответствовать скорости C ++ из-за этого промежуточного шага, однако мне сказали, что последние компиляторы («горячая точка») могут достичь этой скорости или даже превзойти ее.

Это нелогично. Использование промежуточного представления по своей сути не снижает производительность. Например, llvm-gcc компилирует C и C ++ через LLVM IR (который представляет собой виртуальную машину с бесконечными регистрами) в собственный код и обеспечивает отличную производительность (часто превосходящую GCC).

Возможно, это скорее вопрос компилятора, чем вопрос языка, но может ли кто-нибудь объяснить на простом английском, как может один из этих языков виртуальных машин работать лучше, чем родной язык?

Вот некоторые примеры:

  • Виртуальные машины с JIT-компиляцией облегчают генерацию кода времени выполнения (например, System.Reflection.Emitв .NET), поэтому вы можете компилировать сгенерированный код на лету на таких языках, как C # и F #, но должны прибегать к написанию сравнительно медленного интерпретатора на C или C ++. Например, для реализации регулярных выражений.

  • Части виртуальной машины (например, барьер записи и распределитель) часто пишутся вручную на ассемблере, потому что C и C ++ не генерируют достаточно быстрый код. Если программа нагружает эти части системы, она может превзойти все, что может быть написано на C или C ++.

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

Я также хотел бы затронуть некоторые проблемы с ответом paercebal, получившим большое количество голосов выше (потому что кто-то продолжает удалять мои комментарии к его ответу), который представляет контрпродуктивно поляризованное мнение:

Обработка кода будет производиться во время компиляции ...

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

... игра с типами выполняется во время компиляции ... эквивалент в Java или C # в лучшем случае болезненно писать, и всегда будет медленнее и разрешаться во время выполнения, даже если типы известны во время компиляции.

В C # это верно только для ссылочных типов и неверно для типов значений.

Независимо от оптимизации JIT, ничто не пойдет так быстро, как прямой доступ указателя к памяти ... если у вас есть непрерывные данные в памяти, доступ к ним через указатели C ++ (то есть указатели C ... давайте отдадим должное Цезарю) будет происходить в разы быстрее чем в Java / C #.

Люди наблюдали, как Java превосходит C ++ в тесте SOR из теста SciMark2 именно потому, что указатели препятствуют оптимизации, связанной с алиасингом.

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


0

Помимо того, что говорили некоторые другие, насколько я понимаю, .NET и Java лучше выделяют память. Например, они могут сжимать память, поскольку она фрагментируется, в то время как C ++ не может (изначально, но может, если вы используете умный сборщик мусора).


Или, если вы используете лучший распределитель C ++ и / или пул объектов. С точки зрения C ++, это далеко не волшебство, и это может сводиться к тому, что «распределение кучи» становится таким же быстрым, как и выделение стека.
paercebal

Если вы всегда будете размещать все в куче, тогда .NET и Java могут работать даже лучше, чем C / C ++. Но вы просто не сделаете этого на C / C ++.
Frunsi 06

0

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

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

Однако преимущество C ++ заключается в том, что он позволяет вам оптимизировать себя, иначе вы застрянете на том, что делает compiler / jvm. Если вы создаете свои собственные контейнеры, пишете собственное управление памятью, которое выровнено, используете SIMD и переходите к сборке здесь и там, вы можете ускорить, по крайней мере, в 2-4 раза по сравнению с тем, что большинство компиляторов C ++ делают самостоятельно. Для некоторых операций 16x-32x. Это с использованием тех же алгоритмов, если вы используете лучшие алгоритмы и распараллеливаете, увеличение может быть значительным, иногда в тысячи раз быстрее, чем обычно используемые методы.


0

Я смотрю на это с нескольких разных точек зрения.

  1. Будет ли управляемый или неуправляемый код работать быстрее, учитывая бесконечное время и ресурсы? Ясно, что ответ заключается в том, что неуправляемый код всегда может, по крайней мере, связать управляемый код в этом аспекте - поскольку в худшем случае вы просто жестко запрограммируете решение с управляемым кодом.
  2. Если вы возьмете программу на одном языке и напрямую переведете ее на другой, насколько хуже она будет работать? Наверное, много для любых двух языков. Большинство языков требуют разных оптимизаций и имеют разные проблемы. Микропроизводительность часто зависит от знания этих деталей.
  3. При ограниченном времени и ресурсах, какой из двух языков даст лучший результат? Это наиболее интересный вопрос, поскольку, хотя управляемый язык может производить немного более медленный код (при условии, что программа разумно написана для этого языка), эта версия, вероятно, будет создана раньше, что позволит потратить больше времени на оптимизацию.

0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.