Sun разработала динамический компилятор нового поколения, генерирующий принципиально иной байт-код

Разработанная Sun технология HotSpot позволяет создавать интерпретируемый байт-код, который выполняется значительно быстрее скомпилированных программ! Возможно ли такое? Почему бы и нет! Технология HotSpot представляет собой уникальную комбинацию виртуальной машины Java (JVM) и компилятора, которая до недавнего времени существовала только в лабораториях. Ее распространение, возможно, позволит побить все рекорды производительности, известные на сегодняшний день. Данная статья знакомит читателя с внутренним строением динамического компилятора Sun. Вы узнаете также, какие приложения HotSpot может выполнять быстрее самых быстрых компиляторов JIT. Кроме того, проводится сравнение технологии HotSpot с ее предшественницами - стандартными виртуальными машинами, обычными компиляторами и компиляторами just-in-time.

ИСХОДНАЯ ТОЧКА: РАЗОГРЕВАЕМ ДВИГАТЕЛИ

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

Компилятор, обрабатывающий исходный код "на лету" (just-in-time compiler, JIT), позволяет работать на скоростях, во много раз превышающих возможности интерпретатора. Существенная разница ощущается даже при работе с интерактивными приложениями. Хотя JIT и уступает по производительности заранее скомпилированному коду, тем не менее он позволяет значительно расширить сферу применения приложений Java. Однако приложениям, требующим высокого быстродействия, по-прежнему не хватает вычислительной мощности. Если программа ведет постоянный диалог с пользователем или часто выполняются операции ввода/вывода, с такой производительностью еще можно смириться. Однако как только приложение сталкивается с трудоемкими операциями обработки графики или с необходимостью выполнения сложных вычислений, производительность падает ниже допустимого уровня.

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

ПОЕХАЛИ! "И ПЛАВИТСЯ АСФАЛЬТ, ПРОТЕКТОРЫ КИПЯТ"

Динамический компилятор HotSpot объединяет в себе лучшие черты JIT-компилятора и интерпретатора. Такая комбинация "два в одном" значительно повышает скорость выполнения кода. Несомненные достоинства концепции динамической компиляции базируются на исследованиях, которые проводились в течение последних 10 лет в Стэнфорде и университете города Санта-Барбара. За эти годы удалось обеспечить технологии достаточную надежность. А появление HotSpot должно способствовать всплеску ее популярности. Помимо динамической компиляции в HotSpot прекрасно реализованы еще два аспекта: "сборка мусора" и синхронизация.

"Сборка мусора" и синхронизация

К сожалению, для повышения скорости работы приложений Java одной оптимизации кода недостаточно. Для того чтобы повысить быстродействие, расширить функциональность и устранить ошибки приложений, необходимо усовершенствовать все механизмы. Наиболее важный фактор, влияющий на производительность системы, - это качество реализации процедур "сборки мусора" и управления потоками. Как показано на диаграмме (рис. 1), до 40% общего времени тратится на выполнение именно этих задач. Этот рисунок достаточно полно отражает сложившиеся тенденции. Важно отметить, что некоторые приложения фактически все время тратят на "сборку мусора" и синхронизацию, в то время как у других расходы на выполнение данных задач крайне незначительны. (Для построения диаграммы брались усредненные значения.)

Picture 1
Рис. 1. Затраты на "сборку мусора", синхронизацию и непосредственное выполнение кода типичных приложений

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

Еще одна процедура, оказывающая весьма существенное влияние на производительность, - это синхронизация потоков. Потоки Java (одновременно выполняемые сегменты кода) позволяют добиться максимальной гибкости. В частности, они дают возможность организовать ввод/вывод через буфер, повышая тем самым скорость выполнения приложения. Механизм многопоточности позволяет разработчикам создавать серверные приложения, одновременно обслуживающие множество клиентов. Однако расходы на синхронизацию могут отнимать до 20% процессорного времени. Более того, оптимизация скомпилированного кода не способна ускорить синхронизацию.

Помимо динамической компиляции технология HotSpot предоставляет усовершенствованный метод сборки мусора, называемый "сборкой мусора разных поколений" (generational garbage collection). Исследования, проводимые в данном направлении, ранее считались сугубо академическими, но уже сегодня их результаты находят широкое применение в языке Java. Идея "сборки мусора разных поколений" заключается в том, что блоки памяти распределяются и освобождаются в хронологической последовательности.

Разделение блоков памяти на различные "поколения" обеспечивает два основных преимущества. Во первых, сборка мусора выполняется последовательно. Вместо того чтобы просматривать всю память за один проход (что очень сильно снижает производительность системы), механизм сборки мусора анализирует лишь небольшое число блоков. Такая технология освобождения памяти позволяет выполнять данную операцию чаще и с меньшими временными потерями, а следовательно, уменьшается и негативное влияние на производительность программы. Это особенно важно при работе с интерактивными приложениями, поскольку пользователи предпочитают получать именно ту производительность, на которую они рассчитывали, при этом вовсе не жалуя "сумасбродные" программы. Кроме того, значительно упрощается сам процесс сборки мусора.

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

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

Как работает динамический компилятор

HotSpot включает в себя как динамический компилятор, так и виртуальную машину, интерпретирующую байт-код (рис. 2). На первом этапе виртуальная машина интерпретирует байт-код, полученный на выходе компилятора Java Compiler (JavaC). По мере его выполнения профилировщик собирает информацию о производительности и выбирает метод, который будет компилироваться. Скомпилированные методы хранятся в кэше "родных" машинных кодов. При очередном вызове метода используется его версия, хранящаяся в виде "родного" машинного кода (если таковая существует). В противном случае производится повторная интерпретация байт-кода. Функция "управления", проиллюстрированная на рисунке, является, по существу, командой косвенного перехода в памяти, которая указывает либо на "родной" код, либо на интерпретатор. (Общий принцип работы выглядит именно так, хотя конкретные детали реализации не раскрываются.)

Picture 2
Рис. 2. Схема работы HotSpot

После первой загрузки байт-кода он выполняется интерпретатором. Профилировщик следит за каждым методом, и если обнаруживается, что выполнение какого-либо метода занимает много времени, HotSpot компилирует и оптимизирует его. При каждом последующем вызове этого метода выполняются уже "родные" машинные инструкции, сгенерированные компилятором.

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

Профилировщики и эвристическая компиляция

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

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

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

Преимущества динамической компиляции

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

Оптимистичная компиляция. Поскольку интерпретатор всегда доступен для выполнения байт-кода, динамический компилятор HotSpot предполагает, что исключительные ситуации и другие сложные для оптимизации случаи можно не учитывать. Если такая ситуация все же возникает, HotSpot может вернуться к интерпретации байт-кода. В статическом компиляторе оптимизация, при которой учитываются все необычные ситуации, представляет собой очень долгий и сложный процесс. Игнорируя подобные ситуации, HotSpot может быстро генерировать оптимизированный код для тех частей программы, выполнение которых наиболее вероятно. Такая тактика дает огромный выигрыш, обеспечивая значительное увеличение производительности при сравнительно небольших затратах на оптимизацию.

Информация этапа выполнения. Еще одно важное преимущество динамической компиляции - это возможность учета информации, которая становится известна лишь на этапе выполнения программы. И здесь детали реализации держатся в секрете. Однако нетрудно догадаться, что если, например, метод вызывается в цикле 10 000 раз, то его необходимо оптимизировать. Если же цикл выполняется всего один раз, оптимизацию можно опустить и выполнить указанный метод с помощью интерпретатора. Поскольку статическому компилятору заранее неизвестно количество проходов цикла, он не в состоянии принимать подобных решений.

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

Использование встроенных методов обеспечивает колоссальный выигрыш в производительности, но при этом нельзя забывать и о соответствующем увеличении объема памяти, занимаемой программой. Если каждый метод приложения будет размещаться в месте его вызова, объем кода возрастет в пять - десять раз. Поэтому HotSpot оптимизирует только критические участки программы. Правило 80/20 гласит, что 80% времени тратится на выполнение 20% кода. Если встроенные методы будут выбираться именно из этих 20%, значительного повышения производительности удастся достичь при сохранении приемлемых размеров кода. В данном случае информация о времени выполнения не столь важна, поскольку HotSpot знает, где находятся эти критические 20%.

К встраиванию методов нужно подходить очень осторожно. Если размеры кода метода достаточно велики, то на вызов и возврат из процедуры уходит лишь небольшая часть общего времени выполнения. В этом случае встраивание явно не оправдывает себя. Технология HotSpot, без сомнения, анализирует верхнюю границу размера метода; в случае если она оказывается превышена, встраивание уже не производится. (Однако подробности, касающиеся этого процесса, не разглашаются.)

И наконец, поскольку HotSpot выполняет оптимистичную компиляцию, встраиванию подвергаются только те методы, вероятность выполнения которых наиболее высока. Таким образом, чтобы не тратить время на разрешение проблем, возникающих достаточно редко, они просто игнорируются. В результате вы получаете максимальную производительность при минимальных затратах на оптимизацию.

Критерии эффективности динамической компиляции

В подавляющем большинстве случаев HotSpot работает значительно быстрее компиляторов JIT, не говоря уже об обычной интерпретации с помощью JVM. Но иногда использование динамического компилятора HotSpot, как и любой другой технологии повышения производительности, не позволяет достичь наилучших показателей. В частности, профилировщик может принять решение об оптимизации части кода сразу после того, как она была выполнена в последний раз. Очевидно, что таких ситуаций избежать невозможно. К счастью, они возникают весьма редко.

Технология HotSpot не очень приспособлена и для обработки чисто вычислительных приложений, в которых операция "сборки мусора" выполняется достаточно редко, нет синхронизации потоков, а объем инициализационного кода очень мал. В качестве примера такого приложения можно привести программу, перемножающую первые 1000 простых чисел и выводящую результат на экран монитора. Хороший JIT-компилятор обеспечивает в этом случае наивысшую производительность, поскольку он не тратит времени на анализ, а выполняет безусловную оптимизацию.

Кроме того, HotSpot отказывается достаточно качественно выполнять искусственные тестовые программы, такие как CaffeineMark. Поскольку подобные искусственные приложения очень сильно отличаются от реальных, для определения всех оцениваемых в них ситуаций придется написать специальный компилятор, очень быстро выполняющий тесты, многие из которых никогда не встретятся в реальных программах (в частности, оптимизацию пустого цикла). В сравнении с ним компилятор, не оптимизирующий неряшливо написанный код, будет выглядеть не лучшим образом, хотя на самом деле он более приспособлен к выполнению реальных задач. (Недавно Sun продемонстрировала специально спроектированный подобным образом компилятор. Он прекрасно "оптимизировал" бесполезные программные конструкции, выполняя их код практически мгновенно. Возможно, мое мнение не совпадает с общепринятым, однако рискну заметить, что искусственные тесты дают лишь весьма приблизительное представление о реальной производительности.)

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

HotSpot в сравнении с "родным" кодом

Проводя сравнение динамической компиляции с другими способами обработки Java-кода, не следует забывать, что это всего лишь разные способы выполнения Java. Скептически настроенный читатель наверняка заметит: "Ну ладно, а как этот механизм работает в сравнении с моим любимым компилятором?" Уже сегодня чаша весов склоняется в пользу HotSpot, а если принять во внимание перспективы... Sun стремится достичь уровня производительности оптимизированной программы на C, и если верить заявлениям представителей компании, она близка к этому, как никогда.

В настоящий момент хорошие JIT-компиляторы не уступают (а возможно, и превосходят) статическим компиляторам C++, обрабатывающим объектно-ориентированные программы (см. статью "Java и C++: тест на быстродействие" в февральском номере JavaWorld Россия). А HotSpot работает быстрее компилятора JIT! Мы пока что еще не достигли производительности скомпилированных C-программ, но уже близки к этому.

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

ФИНИШ? НУ ЧТО ВЫ, ГОНКА ТОЛЬКО НАЧИНАЕТСЯ!

HotSpot - это первая работающая реализация динамического компилятора для языка Java. Корректность его работы подтверждена испытаниями. Сейчас для того, чтобы добиться абсолютно оптимальной производительности, необходимо последовательно совершенствовать технологию. Sun считает, что уже в ближайшие несколько лет Java-программы смогут выполняться столь же быстро, как и оптимизированные программы на C!

Это поистине впечатляет. Если платформенно-независимый код сможет опередить по производительности даже оптимизированный "родной", это будет означать, что в ближайшие несколько лет на языке Java смогут создаваться фактически любые приложения. Java перестанет быть языком написания апплетов!


Эрик Армстронг стал профессиональным программистом еще до появления первых персональных компьютеров. Он занимался разработкой программ, использующих элементы искусственного интеллекта; системных библиотек; приложений реального времени, а также систем делового назначения. С Армстронгом можно связаться по адресу eric.armstrong@javaworld.com.

КАК РАБОТАЮТ КОМПИЛЯТОРЫ И ИНТЕРПРЕТАТОРЫ

В данном разделе приводится историческая справка о компиляторах и интерпретаторах различных языков, а также об интерпретаторах байт-кода.

Машинные коды и язык ассемблера

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

Огромный шаг вперед был сделан, когда компьютер научили транслировать команды, записанные на языке ассемблера. Этот язык полностью дублирует машинные инструкции, но вместо набора цифр загружает в память компьютера соответствующие им мнемонические коды (например, "LDA"). Хотя между машинными и мнемоническими кодами и существует однозначное соответствие, прогресс заключался в том, что теперь компьютер мог самостоятельно переводить программу на ассемблере в машинные команды. На рис. 3 показано, как ассемблерная команда LDA преобразуется в машинную команду 1378, регистру "A" присваивается номер 00, а переменная N размещается в памяти по адресу 1000. Данный пример весьма условно иллюстрирует то, что происходит в действительности, но тем не менее дает понять, каким образом инструкции ассемблера переводятся в машинный код.

Picture 3
Рис. 3. Операторы языка ассемблера один к одному транслируются в цифровые инструкции машинного кода

Как работает статический компилятор

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

Результаты компиляции нескольких исходных файлов позже компоновались в единый исполняемый модуль. Для этого необходимо было как минимум объединить программу с предварительно откомпилированными функциями системных библиотек. (Присоединение библиотечных функций может выполняться и на этапе выполнения программы. Такие библиотеки называют динамическими - dynamically linked libraries, или DLL.)

Picture 4
Рис. 4. Компилятор транслирует каждый оператор языка высокого уровня в несколько инструкций. Компоновщик объединяет результаты нескольких компиляций (в том числе библиотеки ранее скомпилированных процедур) и создает единую исполняемую программу

Процесс компиляции разбивается на четыре основных этапа: лексический анализ, грамматический разбор, генерацию кода и оптимизацию. На этапе лексического анализа компилятор выделяет в исходном файле отдельные лексемы. Например, в операторе if (myVal == 123) отдельными лексемами являются: "if", "(", "myVal", "==", "123", ")" и ".". На этапе грамматического разбора отдельные лексемы объединяются в операторы. Если компилятор не может распознать смысл оператора, выдается сообщение об ошибке. Если же смысл оператора ясен, осуществляется переход к следующему этапу - генерации кода.

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

Как работает интерпретатор языка

Компилятор генерирует очень эффективный код, но для выполнения им своих задач требуется дополнительное время. Причем, чем выше уровень оптимизации, тем больше времени уходит на компиляцию. Такой процесс не вызывает особых затруднений при однократной трансляции программы, но для начинающих программистов он становится настоящим мучением. Для того, чтобы отладить небольшой модуль, им приходится компилировать его по 30-40 раз. Столь большие затраты убивают всякое желание писать программы.

Picture 5
Рис. 5. Интерпретатор языка представляет собой циклическую систему. Он выбирает очередную инструкцию из входного потока, анализирует и выполняет ее. Затем выбирается следующая инструкция. Процесс продолжается до тех пор, пока не будут выполнены все инструкции или в очередной инструкции не появится указание завершить работу.

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

Командно-ориентированные операционные системы (например, DOS или CSH) являются такими же интерпретаторами, выполняющими ограниченный набор инструкций. Интерпретатор в цикле анализирует вводимую команду, выполняет ее и переходит в режим ожидания новой инструкции. Типичный интерпретатор команд языка Basic работает аналогичным образом. Как вы уже наверное заметили, оптимизации результирующего кода не производится, поэтому интерпретатор не отличается особой эффективностью. Поскольку каждая инструкция выполняется что называется "в лоб", результат одной команды может быть сохранен, только чтобы извлечь его следующей инструкцией. Кроме того, "чистый" интерпретатор последовательно разбирает и анализирует каждую строку входного потока. Это не так уж плохо, если вы выполняете отдельные команды или маленькие программы, однако быстродействие большого приложения в этом случае значительно снижается.

Как работает интерпретатор байт-кода

Двадцать лет назад ученые Калифорнийского университета в Сан-Диего разработали язык UCSD Pascal, в котором были применены те же принципы, что и в сегодняшнем Java. Программы очень эффективно выполнялись с помощью интерпретатора байт-кода. На рис. 6 представлена схема работы интерпретатора байт-кода.

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

Сначала исходный текст программы преобразуется в набор байт-кодов. Если исходный текст написан на Java, то трансляция выполняется специальным "компилятором" Java -javac. Байт-коды представляют собой машинные команды несуществующего в природе компьютера, называемого виртуальной машиной. После этого на реальной машине запускается интерпретатор, понимающий эти инструкции. Интерпретатор байт-кода выполняет анализируемые им команды на реальном процессоре. Примером такого интерпретатора является виртуальная машина Java (JVM). Лексический и грамматический разбор исходного текста программы в этом случае производится на этапе формирования байт-кода. Поэтому интерпретатор байт-кода работает значительно быстрее интерпретатора обычного языка. Результирующий код занимает значительно меньший объем памяти по сравнению с полностью скомпилированной программой. Но самым, пожалуй, важным является свойство переносимости байт-кода. Он может выполняться на любом компьютере, для которого создана виртуальная машина, способная обрабатывать байт-код.

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


КАК РАБОТАЕТ КОМПИЛЯТОР JUST-IN-TIME (JIT)

Компиляция "на лету" (just-in-time) позволяет значительно повысить скорость выполнения байт-кода, поскольку компилятор перед выполнением преобразует его в машинные команды. В данном разделе кратко рассматривается работа компилятора JIT.

Особенности компилятора JIT

Компилятор JIT сначала транслирует байт-код в машинные команды, а затем выполняет их. При этом интерпретации байт-кода на этапе выполнения уже не происходит. Данный процесс представлен на рис. 7.

Picture 7
Рис. 7. На первом этапе компилятор just-in-time (JIT) транслирует байт-код в машинные команды и выполняет оптимизацию. После этого полученные машинные коды запускаются на выполнение. Части программы, которые не выполняются, никогда не компилируются, поэтому компилятор JIT не тратит время на оптимизацию бесполезного кода.

Машинные команды хранятся только в памяти и не записываются на диск. (Вот почему на рисунке они находятся внутри пунктирного контура.) При следующем запуске программы процесс трансляции байт-кода в машинные инструкции полностью повторяется. В результате байт-код сохраняет свою переносимость, но выполняется значительно быстрее по сравнению с обычным интерпретатором (в некоторых случаях быстродействие может возрастать в 50 раз). Как правило, такой скорости оказывается вполне достаточно для работы большинства интерактивных приложений и программ, осуществляющих интенсивный ввод/вывод.

Достоинства и недостатки компилятора JIT

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

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


ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ

  • Более подробную информацию о технологии HotSpot и проектировании приложений, позволяющих достичь максимальной производительности, можно найти по адресу:
    http://java.sun.com/javaone/sessions/slides/TT06/title.htm
  • Процесс "сборки мусора" описан в следующих документах:
    http://www.dcs.gla.ac.uk/~sansom/1993_gen-gc-for-haskell.html http://csgrad.cs.vt.edu/~groener/courses/briefing.shtml
  • Дополнительную информацию о синхронизации потоков можно найти по адресам:
    http://www.javaworld.com/jw-07-1997/jw-07-hood.html
    http://www.math.utah.edu/java/native/implementing/threadsync.html
    http://www.javasoft.com/docs/white/langenv/Threaded.doc2.html
  • Подробная информация о компиляторе JIT, разработанном компанией Symantec, Windows-версия которого вошла в состав Sun JDK, находится по адресу:
    http://www.symantec.com/jit/jit_readme.html
  • Сведения о компиляторе JIT, созданном корпорацией Sun для операционной системы Solaris, приведены на странице:
    http://www.sun.com/solaris/jit/
  • С планами корпорации Sun в отношении технологии HotSpot можно ознакомиться по адресу:
    http://www.sun.com/smi/Press/sunflash/9712/sunflash.971210.5.html