Виртуальная Java-машина
Java в кремнии
RISC-архитектура Java-процессора
Конвейер PicoJava I
Аппаратный стек
Ускорение стека
Производительность PicoJava I

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

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

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

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

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

Виртуальная Java-машина

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

Байт-коды языка Java разрабатывались для обеспечения компактности программ, которая достигается с помощью минимального количества регистров и максимального использования указателей. Средняя команда на языке Java имеет длину всего 1,8 байта, в то время как команды классических RISC-процессоров - около четырех байт. Java-машина имеет стековую архитектуру, поскольку операнды передаются через регистр процессора "стек операндов". Это позволяет уменьшить длину команды, так как она занимает всего один байт, сопровождаемый (если необходимо) номером операнда - 0, 1, 2, 3 и так далее.

Java-машина обрабатывает следующие типы данных:

    byte - байты,
    short - двухбайтные целые числа,
    integer - четырехбайтные целые числа,
    long - восьмибайтные целые числа,
    float - четырехбайтные вещественные числа,
    double - восьмибайтные вещественные числа,
    char - двухбайтные символы,
    object - четырехбайтные ссылки на объекты,
    returnAddress - четырехбайтные адреса возврата из метода.

Она имеет следующие регистры:

    pc - счетчик команд, указывающий на код операции, которая будет выполняться следующей,
    vars - регистр для доступа к локальным переменным текущего метода,
    optop - указатель на стек операндов (так как основная часть операций выполняются именно через стек),
    frame - указатель на структуру окружения времени выполнения.

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

Java-машина имеет следующий набор команд:

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

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

Java в кремнии

До недавнего времени виртуальные Java-машины были действительно виртуальными, то есть Java-программы интерпретировались или компилировались на универсальных компьютерах. В случае интерпретации (рисунок 1, способ 1 ) выполнение программы сильно замедляется, так как универсальный процессор тратит время на преобразование Java-программ в свой собственный код. В случае же компилирования "на лету" (Just In Time - JIT) (рисунок 1, способ 2,), размер программы увеличивается в три или более раз. Если компьютер имеет ограниченный объем памяти, то исполнение Java-программ может привести к серьезным проблемам.

Picture 1

Рисунок 1.
Три способа выполнения байт-кода Java.

Но положение изменилось, когда в октябре 1996 г. фирма Sun Microelectronics выпустила первый Java-процессор - PicoJava I. Архитектура JavaChip_, разработанная Sun Microelectronics, позволяет проектировать процессоры, оптимизированные для быстрого выполнения Java-программ в небольшом объеме памяти. Процессор PicoJava I отвечает всем требованиям, указанным в спецификации виртуальной Java-машины. Поскольку Java-программы на новом процессоре можно выполнять без предварительной обработки, то это позволяет избежать интерпретации или компиляции Java-программ (рисунок 1, способ 3).

RISC-архитектура Java-процессора

Архитектура процессора PicoJava I показана на рисунке 2. При создании процессора, который эффективно реализует "в железе" виртуальную Java-машину, конструкторы PicoJava I использовали RISC-технологию, успешно применяемую специалистами фирмы Sun Microsystems в течение последних 15 лет. Процессор можно легко настроить на различную длину кэша команд и данных, а также включить или выключить вещественный вычислитель.

Picture 2

Рисунок 2.
Блок-схема процессора PicoJava I.

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

Во время работы процессора байт-коды Java записываются в кеш команд, размер которого может меняться от нуля до 16 Кбайт. Хотя командный кеш в PicoJava I и меньше по размеру, чем в других RISC-процессорах, но так как в среднем Java-команда короче, то общий эффект остается примерно таким же. Затем байт-коды передаются в буфер команд, который имеет размер 12 байт. Одновременно в него могут быть записано не более четырех байт, а прочитано - пять. Поскольку большинство команд имеет длину 1,8 байта, то за один такт в процессор может быть загружено сразу несколько команд. Пять начальных байт буфера команд могут быть декодированы и переданы на следующую стадию конвейера для дальнейшей обработки.

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

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

Конвейер PicoJava I

RISC-конвейер в PicoJava I, как и аналогичные конвейеры других RISC-процессоров, обрабатывает команды в четыре этапа (рисунок 3).

Picture 3

Рисунок 3.
Четыре этапа конвейерной обработки в процессоре PicoJava I.

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

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

Аппаратный стек

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

Стек PicoJava I в основном используется для хранения информации о текущих методах (рисунок 4). Виртуальная машина создает для каждого метода отдельную структуру, называемую временем выполнения. Эта структура содержит параметры метода, локальные переменные и окружение времени выполнения, где указан вектор возврата, возвращаемые переменные и константы, и другая необходимая для реализации виртуальной Java-машины информация.

Picture4

Рисунок 4.
Архитектура стека PicoJava I.

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

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

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

Picture 5

Рисунок 5.
Механизм защиты от переполнения стекового кэша.

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

Ускорение стека

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

Для повышения производительности процессор PicoJava I использует совместное исполнение команд. Часто за командой, которая копирует данные из локальной переменной в вершину стека, следует команда обработки загруженных данных. Конвейер команд отслеживает такую ситуацию и обрабатывает эти две команды вместе. Составная команда выполняется так, будто локальная переменная уже была загружена в вершину стека. И только в случае если локальной переменной не оказалось в стековом кэше, совместная обработка команд невозможна.

Моделирование совместной обработки команд показывает, что с ее помощью можно устранить до 60 % лишних операций. Анализ проводился на основе нескольких эталонных программ (рисунок 6), выбиравшихся из широкого круга программ, которые можно реализовать на PicoJava I. Диаграмма показывает, что большой процент времени тратится на команды работы со стеком, такие как передача данных внутри стека, их дублирование, запись в стек констант и обмен данными между стеком и локальными переменными. Поэтому можно значительно ускорить работу программ, если использовать метод совместной обработки команд (рисунок 7). В результате процентное соотношение команд почти аналогично распределению инструкций для универсального RISC-процессора.

Picture 6

Рисунок 6.
Распределение типов команд без учета совместной обработки.

Picture 7

Рисунок 7.
Распределение команд с использованием метода совместной обработки.

Производительность PicoJava I

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

Тесты на производительность PicoJava I показывают, что время и усилия, затраченные на разработку этого процессора, были потрачены не зря. В эталонных тестах PicoJava I работал в 15-20 раз быстрее, чем 486 с интерпретатором (с той же частотой), и - в 5 раз быстрее Pentium с JIT-компилятором (рисунок 8).

Picture 8

Рисунок 8.
Производительность PicoJava I.

  • Javac. Компилятор Java - хороший тест для проверки взаимодействия Java-программы с операционной средой. Java-компилятор, входящий в комплект JDK 1.0.2, представляет собой объектную программу, содержащую более 25000 строк текста на языке Java и использующую 170 различных классов. Размер javac приблизительно 422 Кбайт.
  • Raytracer. Это более традиционный вычислительный тест, использующий большое количество вещественных операций. Raytracer генерирует изображение динозавра, который стоит на прозрачном столе. Размер изображения -100x100 точек, а количество генерируемых треугольников - 1400. Это сложная объектная программа, содержащая 3500 строк Java-кода и использующая 32 различных класса. Ее размер приблизительно 36 Кбайт.
  • Язык Java разрабатывался специально для передачи программ по сетям, именно поэтому написанные на нем программы очень компактны. А высокая производительность PicoJava I, возможности настройки этого процессора и его небольшая стоимость делают эту технологию наиболее приемлемым вариантом развития сети и разработки небольших интеллектуальных устройств. Появление Java-процессора открывает новые возможности для использования компьютерных сетей и привычных бытовых приборов.


    Валерий Коржов - сотрудник компании Jet Infosystems. С ним можно связаться по адресу oskar@jet.msk.su.