Главная arrow Средства разработки arrow Есть ли у Java альтернативы?
Как начинался компьютер
Компьютерная революция
Двоичный код
Разработки военных лет
Интегральные микросхемы
Микрокомпьютер
Персоны
Сеть
Язык компьютера
Развитие ПО
Гибкие системы
Средства разработки
Информатика
Вычислительная наука
Операционные системы
Искусственный интеллект
Предыстория
Поиск
Знания и рассуждения
Логика
Робототехника
 

 
Есть ли у Java альтернативы? Печать

По отношению к переносимому ПО, распространяемому через Internet, Java, на первый взгляд, занимает положение стандарта де-факто. Причем положение достаточно прочное. В то же время, как это ни удивительно, альтернативу для Java-платформы не так уж и сложно обеспечить за счет использования механизма подключаемых модулей (plug-in), поддерживаемого большинством коммерческих Web-браузеров.

В настоящее время мы разрабатываем разностороннюю инфраструктуру для мигрирующих программных компонентов (mobile software components). Это долгосрочное исследование, и напрямую оно не связано ни с Java, ни с World Wide Web. Однако, чтобы продемонстрировать возможности технологии, не так давно мы начали небольшой проект Juice, целью которого является расширение сферы действия нашей платформы для мигрирующего кода на мир коммерческого применения Internet. (Если Java ассоциируется с горячим кофе, то Juice - с холодным яблочным соком (juice по-английски - "сок").

Juice реализуется в виде подключаемого модуля, генерирующего машинный код на лету. И хотя наш формат представления и архитектура исполняющей системы принципиально отличаются от технологии Java, как только соответствующий подключаемый модуль Juice будет установлен на ПК с Windows или Mac OS, пользователь уже не сможет отличить апплеты, написанные на Java, от апплетов, написанных на Juice. Эти два вида мирно сосуществуют на одной Web-странице.

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

Введение

В рекордно короткие сроки технология Java компании Sun Microsystems стала почти синонимом переносимого ПО, способного распространяться через Internet. Господствующие высоты Java предопределены тем, что встроенный механизм поддержки формата представления кода (виртуальная Java-машина, JVM) теперь не только является составной частью практически каждого вида браузеров, но даже претендует на размещение в ядре различных ОС. Интересно отметить, что, хотя Java принята большинством Internet-сообщества в качестве стандарта де-факто для представления исполняемого кода, все же имеется удивительно простой способ создания альтернативной платформы даже в рамках нынешних коммерческих браузеров.

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

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

Технология Juice, как и Java, включает три составляющие:

  • язык программирования и API-интерфейс, с помощью которого реализуются апплеты;
  • нейтральный по отношению к архитектуре формат представления кода;
  • среду для выполнения Juice-апплетов, которая в нынешней реализации Juice поставляется в виде подключаемого модуля, генерирующего машинный код на лету (on-the-fly).

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

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

Программирование Juice-апплетов

Juice-апплеты программируются на языке Oberon (точнее говоря, на языке Oberon-2, созданном Никлаусом Виртом (Niklaus Wirth) и Ханспетером Мессенбоком (Hanspeter Moessenboeck) и являющемся исправленным диалектом языка Oberon. - Прим. ред.). Это прямой преемник языков Pascal и Modula-2, которые также были созданы Никлаусом Виртом. Oberon (разработанный в 1988 г.) по своему духу удивительно близок языку Java: он тоже опирается на принципы простоты и надежности. Строгая типизация достигается в Oberon благодаря обязательной проверке выхода индекса массива за границу диапазона и запрету адресной арифметики. Для автоматизации управления памятью используется механизм сборки мусора, кроме того, предоставляются средства ведения модульности на уровне исходного текста с обеспечением динамической загрузки модулей. Если взять крайне грубое приближение, то Oberon можно рассматривать как подмножество Java с синтаксисом Pascal. Правда, не следует забывать о том, что Oberon был реализован на несколько лет раньше Java.

Oberon - гораздо более компактный язык, чем Java. Ведь он задумывался как квинтэссенция языка программирования. Вот почему Oberon не имеет встроенной поддержки параллельного программирования. В этом аспекте мы согласны с Тедом Льюисом (Ted Lewis), который считает схему, принятую в Java, неудовлетворительной. В рамках нашего проекта мы не пытались вносить собственные изменения в язык Oberon, а потому изучали лишь те апплеты, которые можно построить совместно с помощью Java и Juice. Здесь хотелось бы отметить, что это единственный побочный эффект нашего выбора, павшего на Oberon, и что принципиальных ограничений на взаимозаменяемость по отношению к Java нет. Более того, ряд нынешних оптимизирующих трансляторов для Java также игнорирует средства параллелизма данного языка, поскольку поддержка программных потоков (threads) приводит к серьезным накладным расходам.

Инструментарий для построения Juice-апплетов является неотъемлемой частью комплекта поставки среды Oberon для Apple Macintosh и Microsoft Windows, который предоставляется бесплатно Швейцарским федеральным технологическим институтом в Цюрихе (ETH Zurich) и Университетом Калифорнии в Ирвине (University of California, Irvine). Наряду с полной реализацией системы Oberon System 3 поставляется и набор специфичных для Juice API-интерфейсов, а также среда, имитирующая в рамках системы Oberon поведение подключаемого к браузеру модуля Juice. Таким образом, Juice-апплеты можно отлаживать и тестировать интерактивно, без выхода из инструментальной системы.

Миграция Juice-апплетов

Формат представления кода Juice, который мы назвали "тонкие бинарники" (slim binaries), опирается на результаты диссертационной работы Михаэля Франца. Формат тонких бинарников значительно отличается от представления, характерного для виртуальных машин, такого как p-код или байт-код Java. В нем не хранится исполняемый код. Вместо этого Juice-формат опирается на древовидное представление программы в том виде (синтаксические деревья), который обычно порождается оптимизирующими компиляторами. Это промежуточное представление затем упаковывается путем слияния изоморфных поддеревьев с использованием классического LZW-алгоритма (вариант Уэлша), адаптированного специально для сжатия деревьев программного кода. За счет этого обеспечивается значительная степень компактности кода (см. диаграмму).

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

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

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

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

Выполнение Juice-апплетов

Единственной частью Juice, которая видна конечному пользователю, является набор специфичных подключаемых модулей, предназначенных для таких браузеров, как Netscape Navigator и Microsoft Internet Explorer. Как только соответствующий подключаемый модуль будет установлен на компьютере, пользователь сможет увидеть Juice-апплеты и начнет выполнять их точно так же, как и в случае Java. А потому после установки подключаемого модуля различия между Juice и Java для пользователя теряются (ручное отключение модуля, правда, требует несколько иных манипуляций).

Модуль Juice содержит динамический кодогенератор, осуществляющий трансляцию из формата тонких бинарников в машинный код соответствующей целевой платформы (PowerPC или Intel 80x86). Трансляция производится до того, как начинает свою работу апплет,

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

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

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

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

Периодическая повторная оптимизация уже исполняемого кода обеспечивает более тонкую настройку выходных характеристик кодогенератора по сравнению с уровнем, который достижим для статической компиляции. Это не только позволяет проводить профилирование данных с учетом следующей итерации в ходе оптимизации кода, но и дает возможность обеспечивать кросс-оптимизацию прикладных программ и тех динамически загруженных библиотек, которыми они пользуются. В настоящее время мы проводим эксперименты с методами глобальной оптимизации, которые появились на заре инкрементальных компиляторов (incremental compilers) и оптимизаторов этапа компоновки (link-time optimizers). Помимо того, в зоне нашего внимания распределение регистров и вставка кода (inlining) на границах модулей, глобальная диспетчеризация инструкций и глобальная оптимизация кэша. Программные системы, расширяемые в процессе работы (run-time extensible systems), вновь бросают вызов старым проблемам. Ведь провести законченный анализ такой системы невозможно, поскольку модули могут подстыковываться к работающей системе в любой момент времени.

Заключение и некоторые прогнозы

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

Механизм подключаемых модулей открывает путь для неограниченно большого числа альтернатив технологии Java, которые будут со временем появляться, постепенно ослабляя превосходство Java. Помимо представленных здесь решений Juice, сильным кандидатом на победу в борьбе за рынок может оказаться технология Inferno, разработанная в компании Lucent Technologies (при условии, что Inferno может быть реализована в виде подключаемого модуля). Наверняка появятся и другие претенденты. Стоит отметить, что каждый подобный модуль может распространяться через Internet с использованием аутентификационного механизма подписи кода. При этом упростится логика одновременной поддержки нескольких конкурирующих форматов представления кода.

Также возможно, и даже желательно, чтобы эталонная Java-платформа распалась на несколько разновидностей. Например, компания Microsoft встроила особый API-интерфейс в собственную реализацию Java и в свой браузер Internet Explorer, который отличается от разработок компании Sun Microsystems (где и была создана исходная версия Java). Это может привести к ситуации, когда различия между наборами библиотек станут неустранимыми, а следовательно, появятся взаимно несовместимые версии Java. Такое различие может быть скрыто от пользователя с помощью того же самого подхода, который мы взяли на вооружение при разработке Juice.