Просто во время компиляции всегда быстрее?
Приветствую всех дизайнеров компиляторов здесь, на Stack Overflow.
В настоящее время я работаю над проектом, который сосредоточен на разработке нового языка сценариев для использования с высокопроизводительными вычислениями. Исходный код сначала компилируется в представление байтового кода. Затем байт-код загружается средой выполнения, которая выполняет агрессивные (и, возможно, трудоемкие) оптимизации для него (которые идут гораздо дальше, чем то, что делают даже большинство "опережающих" компиляторов, в конце концов, в этом весь смысл проект). Имейте в виду, что результатом этого процесса по-прежнему остается байт-код.
Затем байт-код запускается на виртуальной машине. В настоящее время эта виртуальная машина реализована с использованием прямой таблицы переходов и насоса сообщений. Виртуальная машина перебирает байт-код с указателем, загружает инструкцию под указателем, ищет обработчик инструкций в таблице переходов и переходит в нее. Обработчик команд выполняет соответствующие действия и, наконец, возвращает управление циклу сообщений. Указатель инструкций виртуальной машины увеличивается, и весь процесс начинается заново. Производительность, которую я могу достичь с помощью этого подхода, на самом деле довольно удивительна. Конечно, код реальных обработчиков команд снова настраивается вручную.
В настоящее время большинство "профессиональных" сред выполнения (таких как Java, .NET и т. Д.) Используют компиляцию Just-in-Time для преобразования байтового кода в собственный код перед выполнением. Виртуальная машина, использующая JIT, обычно имеет гораздо лучшую производительность, чем интерпретатор байтового кода. Теперь вопрос в том, что, в сущности, все, что делает интерпретатор, - это загружает инструкцию и ищет цель перехода в таблице переходов (помните, что сам обработчик инструкций статически скомпилирован в интерпретатор, так что это уже нативный код), будет ли использовать Компиляция Just-in-Time приводит к увеличению производительности или фактически снижает производительность? Я не могу себе представить, что таблица переходов интерпретатора настолько сильно снижает производительность , чтобы компенсировать время, потраченное на компиляцию этого кода с использованием JITer. Я понимаю, что JITer может выполнять дополнительную оптимизацию кода, но в моем случае очень агрессивная оптимизация уже выполняется на уровне байтового кода перед выполнением. Как вы думаете, я мог бы получить больше скорости, заменив интерпретатор JIT-компилятором? Если так, то почему?
Я понимаю, что реализация обоих подходов и сравнительный анализ обеспечат наиболее точный ответ на этот вопрос, но это может не стоить времени, если есть четкий ответ.
Благодарю.
3 ответа
Ответ заключается в соотношении сложности инструкций с однобайтовым кодом к издержкам таблицы переходов. Если вы моделируете операции высокого уровня, такие как большие умножения матриц, то небольшие издержки будут незначительными. Если вы увеличиваете одно целое число, то, конечно, это сильно влияет на таблицу переходов. В целом, баланс будет зависеть от характера более срочных задач, для которых используется язык. Если он предназначен для языка общего назначения, то для всех более полезно иметь минимальные накладные расходы, поскольку вы не знаете, что будет использоваться в узком цикле. Чтобы быстро оценить потенциальное улучшение, просто сравните несколько вложенных циклов, выполняя некоторые простые операции (но те, которые нельзя оптимизировать), по сравнению с эквивалентной программой на C или C++.
Когда вы используете интерпретатор, кэш кода в вашем процессоре кэширует код интерпретатора; не байт-код (который может быть кэширован в кеше данных). Поскольку кэши кода в 2-3 раза быстрее, чем кэши данных, IIRC; Вы можете увидеть повышение производительности, если JIT компилируется. Кроме того, исходный, реальный код, который вы выполняете, вероятно, PIC; то, чего можно избежать для кода JITted.
Все остальное зависит от того, насколько оптимизирован байт-код, ИМХО.
JIT теоретически может оптимизировать лучше, так как он имеет информацию, недоступную во время компиляции (особенно о типичном поведении во время выполнения). Так, например, он может лучше прогнозировать переходы, по мере необходимости развертывать циклы и т. Д.
Я уверен, что ваш подход с перемычками в порядке, но я все еще думаю, что он будет работать довольно плохо по сравнению с простым кодом C, не так ли?