Чем JIT-компилятор отличается от обычного компилятора?

22

Было много ажиотажа по поводу JIT-компиляторов для таких языков, как Java, Ruby и Python. Чем JIT-компиляторы отличаются от компиляторов C / C ++ и почему компиляторы, написанные для Java, Ruby или Python, называются JIT-компиляторами, а компиляторы C / C ++ просто называются компиляторами?

Кен Ли
источник

Ответы:

17

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

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

Виктор Стафуса
источник
Существуют ли JIT-компиляторы, которые отслеживают пропуски кэша и соответствующим образом адаптируют свою стратегию компиляции? @Victor
Мартин Бергер
@MartinBerger Я не знаю, делают ли это какие-нибудь доступные JIT-компиляторы, но почему бы и нет? По мере того как компьютеры становятся все более мощными и развиваются компьютерные науки, люди могут применять к программе больше оптимизаций. Например, когда родился Java, он очень медленный, может быть, в 20 раз по сравнению со скомпилированными, но теперь производительность не сильно отстает и может быть сопоставима с некоторыми компиляторами
phuclv
Я слышал это в каком-то блоге давно. Компилятор может компилироваться по-разному в зависимости от текущего процессора. Например, он может автоматически векторизовать некоторый код, если обнаружит, что ЦП поддерживает SSE / AVX. Когда доступны новые SIMD-расширения, они могут компилироваться в более новые, поэтому программисту не нужно ничего менять. Или, если это может организовать операции / данные, чтобы использовать в своих интересах больший кэш или уменьшить кэш-промах
phuclv
@ LưuVĩnhPhúc Chào! Я счастлив в это поверить, но разница в том, что, например, тип процессора или размер кэша являются чем-то статичным, который не изменяется в течение всего вычисления, поэтому может быть легко сделан статическим компилятором тоже. Кэш пропускает OTOH очень динамично.
Мартин Бергер
12

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

Оптимизация кода, безусловно, может выполняться «классическими» компиляторами, но обратите внимание на основное отличие: JIT-компиляторы имеют доступ к данным во время выполнения. Это огромное преимущество; эксплуатировать его должным образом может быть трудно, очевидно.

Рассмотрим, например, такой код:

m(a : String, b : String, k : Int) {
  val c : Int;
  switch (k) {
    case 0 : { c = 7; break; }
    ...
    case 17 : { c = complicatedMethod(k, a+b); break; }
  }

  return a.length + b.length - c + 2*k;
}

Обычный компилятор не может сделать слишком много с этим. Однако JIT-компилятор может обнаружить, что он mвызывается только k==0по какой-то причине (подобные вещи могут происходить по мере изменения кода с течением времени); затем он может создать уменьшенную версию кода (и скомпилировать его в нативный код, хотя я концептуально считаю это второстепенным):

m(a : String, b : String) {
  return a.length + b.length - 7;
}

В этот момент он, вероятно, даже встроит вызов метода, поскольку теперь он тривиален.

По-видимому, Sun отклонил большинство оптимизаций, javacиспользуемых в Java 6; Мне сказали, что из-за этих оптимизаций JIT сильно мешал, и в конце концов наивно скомпилированный код работал быстрее. Пойди разберись.

Рафаэль
источник
Кстати, при наличии стирания типов (например, дженерики в Java) JIT фактически находится в невыгодном положении по сравнению с типами.
Рафаэль
Таким образом, среда выполнения сложнее, чем для скомпилированного языка, потому что среда исполнения должна собирать данные для оптимизации.
saadtaame
2
Во многих случаях JIT не сможет заменить mверсию, которая не проверяет, kпоскольку он не сможет доказать, mчто никогда не будет вызван с ненулевым k, но даже без возможности доказать, что он может заменить это с static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for м ...} else { ... consider other optimizations...}.
суперкат
1
Я согласен с @supercat и думаю, что ваш пример на самом деле довольно обманчив. Только если это k=0 всегда означает, что это не является функцией ввода или среды, можно отказаться от теста, но для его определения требуется статический анализ, который очень дорог и, следовательно, доступен только во время компиляции. JIT может победить, когда один путь через блок кода выбирается намного чаще, чем другие, и версия кода, специализированная для этого пути, будет намного быстрее. Но JIT все равно будет выдавать тест, чтобы проверить, применяется ли быстрый путь , и, если нет, выбрать «медленный путь».
j_random_hacker
Пример: функция принимает Base* pпараметр и через него вызывает виртуальные функции; Анализ во время выполнения показывает, что фактический объект, на который указывает всегда (или почти всегда), кажется, имеет Derived1тип. JIT может создать новую версию функции со статически разрешенными (или даже встроенными) вызовами Derived1методов. Этому коду будет предшествовать условие, которое проверяет, pуказывает ли указатель vtable на ожидаемую Derived1таблицу; если нет, вместо этого он переходит к исходной версии функции с более медленными динамически разрешаемыми вызовами методов.
j_random_hacker