Когда java быстрее, чем С++ (или когда JIT быстрее, чем предварительно скомпилирован)?

Возможный дубликат:
JIT-компилятор против автономных компиляторов

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

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

ПРИМЕЧАНИЕ. Этот вопрос not о сравнении Java с С++. О возможностях компиляции JIT. Пожалуйста, не пылайте. Я также не знаю ни одного дубликата. Пожалуйста, укажите их, если вы находитесь.

Ответы

Ответ 1

На практике вы, вероятно, найдете, что ваш наивно написанный Java-код превосходит ваш наивно написанный код С++ в этих ситуациях (все, что я лично наблюдал):

  • Множество небольших распределений/освобождений памяти. У крупных JVM есть чрезвычайно эффективные подсистемы памяти, и сбор мусора может быть более эффективным, чем требовать явного освобождения (плюс он может смещать адреса памяти и, если он действительно хочет).

  • Эффективный доступ через глубокие иерархии вызовов методов. JVM очень хорошо справляется с чем-либо, что не является необходимым, обычно лучше в моем опыте, чем большинство компиляторов С++ (включая gcc и icc). Частично это связано с тем, что он может выполнять динамический анализ во время выполнения (т.е. Он может переопределять и только деоптимизировать, если обнаруживает проблему).

  • Инкапсуляция функциональности на небольшие недолговечные объекты.

В каждом случае, если вы приложите усилие, С++ может улучшиться (между свободными списками и блочной выделенной/освобожденной памятью, С++ может бить систему памяти JVM почти в каждом конкретном случае, с дополнительным кодом, шаблонами и умные макросы, вы можете эффективно свернуть стеки вызовов, и вы можете иметь небольшие частично инициализированные объекты, связанные с стеками на С++, которые превосходят объектно-ориентированную модель JVM). Но вы, вероятно, не хотите прикладывать усилия.

Ответ 2

Некоторые примеры:

  • Компилятор JIT может создавать машинный код с очень специфичным процессором, например, новейшие расширения SSE, которые не будут использоваться в предварительно скомпилированном коде, который должен запускать один широкий спектр процессоров.
  • JIT знает, когда виртуальный метод (по умолчанию в Java) нигде не перезаписывается и, таким образом, может быть встроен (хотя для этого требуется возможность un-inline его при загрузке нового класса, который перезаписывает метод; Java JIT-компиляторы действительно делают это).
  • В связи с этим escape-анализ позволяет видеть оптимизацию конкретной ситуации.

Ответ 3

Википедия: http://en.wikipedia.org/wiki/Just-in-time_compilation#Overview

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

  • Компиляция может быть оптимизирована для целевого ЦП и модели операционной системы, в которой выполняется приложение. Например, JIT может выбирать инструкции CPU SSE2, когда он обнаруживает, что ЦП поддерживает их. Чтобы получить этот уровень специфичности оптимизации со статическим компилятором, нужно либо скомпилировать двоичный файл для каждой предполагаемой платформы/архитектуры, либо включить несколько версий частей кода в пределах одного двоичного файла.

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

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

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