Каковы преимущества компиляции точно в срок по сравнению с опережающей компиляцией?

В последнее время я думал об этом, и мне кажется, что большинство преимуществ JIT- компиляции следует более или менее отнести к промежуточному формату, и что само по себе джиттинг не является хорошим способом генерирования кода.

Итак, вот основные аргументы pro-JIT- компиляции, которые я обычно слышу:

  1. Своевременная компиляция обеспечивает большую мобильность. Разве это не связано с промежуточным форматом? Я имею в виду, что ничто не мешает вам скомпилировать ваш виртуальный байт-код в собственный байт-код, как только вы получите его на своем компьютере. Переносимость является проблемой на этапе "распространения", а не на этапе "работы".
  2. Хорошо, тогда как насчет генерации кода во время выполнения? Ну, то же самое относится. Ничто не мешает вам интегрировать компилятор точно в срок для реальной необходимости точно в вашу нативную программу.
  3. Но среда выполнения в любом случае компилирует его в собственный код и сохраняет полученный исполняемый файл в каком-то кеше где-то на жестком диске. Да, конечно. Но она оптимизирует вашу программу в условиях нехватки времени и с этого момента не делает ее лучше. Смотрите следующий абзац.

Не то чтобы преждевременная компиляция тоже не имела никаких преимуществ. Компиляция точно вовремя имеет временные ограничения: вы не можете заставить конечного пользователя ждать вечно, пока ваша программа запускается, поэтому у него есть компромисс, который нужно где-то сделать. Большую часть времени они просто оптимизируют меньше. У моего друга было свидетельство того, что встраивание функций и развертывание циклов "вручную" (запутывание исходного кода в процессе) положительно сказалось на производительности его программы C# для обработки чисел; Выполнение того же с моей стороны, когда моя C- программа выполняла ту же задачу, не дало никаких положительных результатов, и я считаю, что это связано с обширными преобразованиями, которые мой компилятор позволял делать.

И все же мы окружены сопряженными программами. C# и Java есть везде, скрипты Python могут компилироваться в какой-то байт-код, и я уверен, что целый ряд других языков программирования делают то же самое. Должна быть веская причина, по которой я скучаю. Так что же делает своевременную компиляцию настолько превосходящей опережающую?


РЕДАКТИРОВАТЬ Чтобы избавиться от некоторой путаницы, может быть важно заявить, что я все для промежуточного представления исполняемых файлов. У этого есть много преимуществ (и действительно, большинство аргументов для компиляции точно в срок - фактически аргументы для промежуточного представления). Мой вопрос о том, как они должны быть скомпилированы в нативный код.

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

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

9 ответов

Решение

Страница инструмента ngen пролила бобы (или, по крайней мере, обеспечила хорошее сравнение нативных изображений с изображениями, скомпилированными JIT). Исполняемые файлы, которые скомпилированы заранее, обычно имеют следующие преимущества:

  1. Нативные образы загружаются быстрее, потому что они не выполняют много действий при запуске и требуют статического объема памяти меньше (память, требуемая компилятором JIT);
  2. Нативные изображения могут совместно использовать код библиотеки, а JIT-скомпилированные изображения - нет.

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

  1. Собственные изображения больше, чем их байт-код;
  2. Собственные изображения должны регенерироваться всякий раз, когда изменяется исходная сборка или одна из ее зависимостей.

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

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

  1. Большая переносимость: результат (байт-код) остается переносимым

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

  3. Улучшения в технологии компиляции могут повлиять на существующие программы. Лучший компилятор C не поможет вам с уже развернутыми программами. Лучший JIT-компилятор улучшит производительность существующих программ. Код Java, который вы написали десять лет назад, сегодня будет работать быстрее.

  4. Адаптация к метрикам времени выполнения. JIT-компилятор может не только смотреть на код и целевую систему, но и на то, как код используется. Он может работать с исполняемым кодом и принимать решения о том, как оптимизировать, например, в соответствии с тем, какие значения обычно имеют параметры метода.

Вы правы в том, что JIT увеличивает стоимость запуска, поэтому для этого есть ограничение по времени, тогда как преждевременная компиляция может занять столько времени, сколько нужно. Это делает его более подходящим для приложений серверного типа, где время запуска не так важно, и "фаза прогрева" до того, как код станет действительно быстрым, приемлема.

Я предполагаю, что можно было бы где-то сохранить результат компиляции JIT, чтобы его можно было использовать повторно в следующий раз. Это дало бы вам опережающую компиляцию для второго запуска программы. Может быть, умные люди из Sun и Microsoft считают, что новый JIT уже достаточно хорош и дополнительная сложность не стоит хлопот.

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

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

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

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

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

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

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

Но не существует такой вещи, как языки только для JIT. Для Java уже давно доступны компиляторы, и совсем недавно Mono представила их в CLR. Фактически, MonoTouch возможен вообще из-за компиляции AOT, так как не-нативные приложения запрещены в магазине приложений Apple.

Кажется, что эта идея была реализована на языке дартс:

https://hackernoon.com/why-flutter-uses-dart-dd635a054ebf

JIT-компиляция используется во время разработки с использованием особенно быстрого компилятора. Затем, когда приложение готово к выпуску, оно компилируется AOT. Следовательно, с помощью передовых инструментов и компиляторов, Dart может обеспечить лучшее из обоих миров: чрезвычайно быстрые циклы разработки, а также быстрое время выполнения и запуска.

Я также пытался понять это, потому что увидел, что Google движется к замене своей виртуальной машины Dalvik (по сути, другой виртуальной машины Java, такой как HotSpot) на Android Run Time (ART), который является компилятором AOT, но Java обычно использует HotSpot, который является JIT-компилятором. По-видимому, ARM примерно в 2 раза быстрее, чем Dalvik... поэтому я подумал: "Почему Java также не использует AOT?". В любом случае, из того, что я могу понять, основное отличие состоит в том, что JIT использует адаптивную оптимизацию во время выполнения, которая (например) позволяет ТОЛЬКО частям выполняемого байт-кода часто компилироваться в собственный код; в то время как AOT компилирует весь исходный код в собственный код, и код меньшего объема выполняется быстрее, чем код большего объема.
Я должен представить, что большинство приложений Android состоят из небольшого количества кода, поэтому в среднем имеет смысл скомпилировать весь исходный код в собственный код AOT и избежать издержек, связанных с интерпретацией / оптимизацией.

Одно из преимуществ JIT, которое я не вижу в этом списке, - это возможность встроить / оптимизировать отдельные сборки /dll /jars (для простоты я собираюсь использовать "сборки" здесь и далее).

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

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

Разница между платформой-браузером-динамической и платформой-браузером заключается в том, как будет скомпилировано ваше угловое приложение. Использование динамической платформы делает угловую отправку компилятора Just-in-Time внешнему интерфейсу, а также вашему приложению. Это означает, что ваше приложение компилируется на стороне клиента. С другой стороны, использование платформы-браузера приводит к тому, что предварительно скомпилированная версия вашего приложения отправляется в браузер. Что обычно означает отправку в браузер значительно меньшего пакета. Angular2-документация для начальной загрузки на https://angular.io/docs/ts/latest/guide/ngmodule.html объясняет это более подробно.

Другие вопросы по тегам