Виртуальные машины
Программно-определяемые процессоры
Зачем нужны виртуальные машины?
В большом количестве случаев удобно работать в терминах тех сущностей, с которыми происходят реальные действия в рамках программного проекта. Это могут быть сложные объекты, коровы, сеялки и даже нефтяные вышки. Однако существует некоторый разрыв с цифровой реальностью - биты и байты компьютера не совсем то, с чем имеет дело человек на самом деле. К тому же уровень системы команд процессора очень низок, и каждое действие с машинным представлением реального объекта занимает немало команд. Кроме того, существует масса технических проблем низкого уровня - отлавливание ошибок, отсутствие мониторинга памяти, оптимизация, затрудняющая отладку. Поэтому в ряде случаев в целях безопасности, простоты отладки и переносимости разрабатывают концепции процессоров, которые на низком уровне исполняют инструкции, работающие со сложными объектами.
Что есть виртуальная машина:
Совокупность аппаратной, программной и интерфейсной спецификаций, полностью определяющих некоторую вычислительную среду.
Зачем нужна виртуальная машина:
Описание предметной области в абстрагированном от конкретной вычислительной платформы виде – средо-ориентированное программирование.
Удобная для ряда применений (не просто приложений) архитектура системы команд
- Стековая
- Объектно-ориентированная
- Предоставление особых программистских моделей и сервисов во время исполнения программы
Выжившие машины и концепции
- Виртуальные процессоры
- Цепные интерпретивные коды (P-код, Forth-машина)
- Виртуальные процессоры с поддержкой парадигмы объектно-ориентированого программирования
- Виртуальные объектно-ориентированные процессоры
- (список далеко не исчерпывающий)
Виртуальный процессор
- Система команд, удобная для исполнения определённых задач пользователя
- Стековый процессор – простейшая переносимая модель исполнения
- Объектно-ориентированная модель исполнения
- Доступ к полям/свойстам экземпляров и классов
- Вызов методов экземпляров и классов
- Автоматическая поддержка связывания в иерархии классов
- Средо-ориентированная модель исполнения программы
- Высокоуровневые среды, оперирующие со сложными понятиями: математические пакеты
- Программирование в терминах среды
- Программисту (часто, специалисту-прикладнику в определённой области) не интересны детали реализации, кроме того, быстродействие не играет решающей роли
- Среда поддерживает ряд дополнительных возможностей, позволяющих упростить разработку, тестирование и сопровождение программы
- Встречный вопрос: а что мешает реализовать в железе виртуальный процессор
- Целесообразность
- Количество потенциальных пользователей
Стековая машина
- Одна шина данных
- 2 стека (данных и возвратов) с операциями push и pop
- Вершина стека данных в отдельном регистре
- Быстродействие является основным минусом данной архитектуры
- Очень простая система команд
- Обращение в память
- Операции со стеком
- Копирование данных между стеками
- Арифметико-логические операции
- Передача управления
- Загрузка констант
Цепные интерпретивные коды
Виртуальная машина Java
- Абстрактная вычислительная машина, в основе лежит стековая машина
- Не предполагает особенных реализаций, специального аппаратного обеспечения или поддержки ОС
- Не имеет ничего общего с языком Java
- Понимает формат .class файлов (байт-коды и символьную информацию) – формат специфицирован стандартом
- Поддержка мультипоточности
- Поддержка общей для потоков кучи и автоматической сборки мусора
- Поддержка верификации кода
Команды ВМ Java
- Типы: поддержка символьных, целых, вещественных данных, адресов
- Работа с памятью
- Загрузка/сохранение локальных переменных
- Загрузка констант на стек
- Арифметика
- 5 арифметических действий с разными типами (тип указан явно)
- Логические операции
- Операции сравнения
- Операции преобразования типов
- Операции над объектами
- Создание экземпляров классов и массивов экземпляров
- Загрузка/сохранение элемента массива
- Загрузка/сохранение полей экземпляра/класса
- Получение сведений о экземпляре
- Операции со стеком операндов
- Инструкции передачи управления
- Вызов методов и возврат из вызова
- Генерация исключений
- Синхронизация потоков
Поддержка ООП в ВМ Java
- Создание экземпляра и массива экземпляров
- Доступ к полям (чтение и запись) экземпляра и класса
- Определение типа экземпляра
- Вызов интерфейсов, методов класса, виртуальных методов (существуют вызовы просто процедур)
- Выход из метода (существует выход просто из процедуры)
- Вход и выход из монитора объекта
- Создание массива
- Дополнительные структуры данных:
- Область метода
- Пул констант
- Сборщик мусора
Платформа Java против стекового микропроцессора
- Платформа (система инструкций + интерфейс стандартных библиотек) против просто набора инструкций
- Стандарт и ещё раз стандарт – кроссплатформенное исполнение как следствие
- Дополнительные внутренние сервисы, которые явно не прослеживаются на базе системы инструкций (менеджмент памяти, загрузчик классов, связывание бибилиотек)
- JIT-компилятор (или аппаратные надстройки типа Jazelle для ARM), позволяющий оптимизировать исполнение кода на RISC архитектуре
- Стековый процессор (например, Java-ориентированный Patriot Scientific PSC-1000A) имеет оптимизированное аппаратное обеспечение для исполнения кода Java
Java на кристалле - JSTAR
Сопроцессор ядра ARM, позволяющий исполнять команды как ARM, так и байт-коды Джава. Изначально служит для ускорения исполнения Java-приложений на встраиваемых устрйоствам, подключенных к Интернет, поскольку имеющиеся ресурсы не позволяют компилировать байт-код Java в машинные команды "на лету".
- 2-х ступенчатый конвейер
- 150 пс задержка для native кода любого процессора (0.18 мкм)
- 1 native инструкция выдаётся за 1 такт
- Собственный счётчик команд
- Не сбрасывает при работе основной конвейер
- Требует некоторого изменения JVM
- Поддерживает прямое исполнение 159 команд Java и таблицу вызовов для неподдержанных команд
- В данном случае JVM служит для интерпретации не-поддержанных команд Java
- Частота до 400 Мгц
- Количество вентилей – 27К – 30К
- Реализации для MIPS, ARM7, ARM9
Java на кристалле - Jazelle
А вот это уже реальный рыночный продукт.
- В целом наследует архитектуру JSTAR, но подходит только к ядру ARM
- В архитектуру вводится состояние «исполнение Java-кода» (24-й бит регистра статуса)
- Стек ограничен 4-мя регистрами, но есть автоматическая загрузка/сохранение стека в память
- Использует счётчик команд основной архитектуры
- Поддежривет аппаратно 140 байт-кодов Java (всего их 228)
- 12К вентилей
- Встраивается в архитектуру ARM9
Закономерный итог - десятикратное увеличение скорости программ, реализованных в байт-коде Java.
Java на кристалле - JVXtreme
Отдельный стековый процессор, с оптимизацией использования стека (folding)
- Аппаратная поддержка 92-х команд Java
- Поддержка Java-мультипоточности
- 64-элеметный стек (каждая ячейка – 32 бита) – более чем достаточно для приложений
- ARM7, ARM9
- 200 МГц (0.18 мкм), 35К вентилей
- Распространяется как Verilog-код (inSilicon)
Java на кристалле – Hot-Shot от Chicory Systems
JIT-в-железе – не больше, не меньше
- Поддержка 148 инструкций
- Технология аналогична Code Morphing от Transmeta
- Генерируется одна инструкция за такт, но сразу выдаётся целый блок
- 4-х ступенчатый конвейер
- Выборка, декодирование, оптимизация, запись
- Генерирование кода представлено автоматом
- Оптимизации проводятся не на основе 1-2 инструкций, а на уровне более длинных последовательностей (поток более чем из 100 байт-кодов)
- Адаптивное распределение регистров
- Умеет даже определять элементы циклических конструкций и условных конструкций
- В кэш-памяти могут содержаться уже откомпилированные версии часто встречаемых последовательностей байт-кода
- 200 Мгц (0.18 мкм)
- 25К вентилей для акселератора Java
Java на кристалле – Espresso и Decaf
Полнофункциональный процессор или сопроцессор
Espresso: суперскаляр, 2 5-ти ступенчатых конвейера, исполняет до 8 инструкций (14 байт-кодов) за такт
Decaf – низкопотребляющая версия Espresso, 1 конвейер, до 4 инструкций за такт (7 байт-кодов), обработка как целых чисел, так и 32 и 64 битовых чисел с плавающей точкой
Java на кристалле – Decaf
- Содержит 64-элементый мультипортовый стек (32 битовые ячейки), с автоматическими пересылки данных в/из памяти при переполнении стека
- Исполняет 90% инструкций Java (кроме «тяжёлых» вычислительных инструкций, вызовов и возвратов из методов, …), отсутствующие инструкции может выполнить центральный процессор или же сам Decaf в Java-режиме
- 150К вентилей (не учитываем кэш-память)
- Не содержит узел предсказания направления перехода
- 200Мгц – 340 Мгц (0.18 мкм)
- Код ядра лицензируется (под 2%-6% от стоимости чипа)
Движение к Java на кристалле
Зачем изобретать велосипед, если можно просто помочь JIT-компилятору
Решение:Thumb-2EE (Execution Envorinment) для Динамических Адаптивных Компиляторов (Smart JIT)
- Всего 8К к ядру ARM7
- CHKA – проверка выхода индекса за границы массива
- HB{L}, HB{L}P – вызов исключения (TRY – CATCH)
- LDR/STR относительно R9/R10 – загрузка переменных и констант метода с проверкой базового указателя на нулевое значение
- По сравнению с существующими наборами инструкций, Java-оптимизированный набор даёт лишь 7% разрастания кода по сравнению с байт-кодом (!) – см. сравнение на следующем слайде
- (Обычно разрастание кода – в 6 раз, хотя – весь вопрос в компиляторе)
Технология .NET и CIL
Всё та же стековая архитектура системы команд – Common Intermediate Language, стандартизован Дальнейшее продвижение к объектно-ориентированной архитектуре виртуальной машины – managed execution. Основная цель - дать разработчику удобный переносимый инструмент, выбить почву из-под ног конкурентов и предоставить эффективный инстрвмент для отлавливания ошибок уменьшая головную боль менеджеру и позволяя снизить требования к квалификации программистов.
Основные особенности:
- Assembly – сборка классов, аналог Java-сборки
- Профили для встраиваемых архитектур (Compact)
- По сравнению с Java используется динамическая проверка типов и «виртуальные инструкции» (отметим, что Бабаян очень давно выдвигал такую идею даже для performance-архитектур
- Дескрипторная абстракция доступа к памяти
- Для связи с «внешними» программами вводится абстракция unmanaged код
- Динамическая проверка типов во время исполнения
- Хранение и использование информации о типах данных, сборках и пр. во время исполнения кода
- Типичная стековая машина
- Особая модель адресации
- Доступ к локальным переменным (ч/з) по номеру, аргументы функции только читаются
- Загрузка констант типизирована
- Все арифметико-логические команды и команды конверсии типов типизированы (и виртуальны) – реально исполняемая команда зависит от комбинации типов операндов на стеке
- Арифметико-логические команды с проверкой на переполнение
- Инструкции доступа к массивам имеют массу проверок
- Проверка плавающих чисел на «конечность»
- Фактически используется стек типов для динамических проверок
- Подавляющее большинство инструкций обращается к метаинформации
- Поддержка «встраиваемого» профиля Compact
- Не факт, что для всех архитектур будет возможно использование JIT
- Метаинформация, минимально необходимая для запуска программы в кодах CIL
- Строки: соответствие дескрипторов адресам строк
- Константы: соответствие идентификаторов констант значениям и типам констант
- Методы: соответствие дескриптора метода адресу кода (или смещения в таблице виртуальных методов), объём локальных переменных, количество аргументов, флаги метода
- Поля объектов: соответствие дескриптора поля смещению поля в объекте и тип данных
- Описание типа: соответствие дескриптора типа, базовый тип, размер объекта, смещение области сохранения данных в экземпляре объекта, адрес информации RTTI
- Описание массива: соответствие дескриптора массива адресу области сохранения, количество элементов массива, тип элементов массива
- и прочее, с чем можно ознакомится после глубокого изучения документации
- Фактически, все описания сущностей абсолютно независимы от системы адресации
Лирическое отступление – слабости JIT
JIT является не панацеей
- «локальный» JIT использовался и 30 лет назад, и результаты были неплохие, однако разработчики почему-то не отбросили идею исполнения «стековых» вычислительных машин прямо на железе
- JIT всё-таки более генератор кода, чем полнофункциональный компилятор
- «большому» компилятору доступна вся информация исходного кода (например, высокоуровневые структуры, прагмы), JIT (в зависимости от промежуточного представления программы) имеет эту информацию с большими потерями
Эффективность JIT зависит от «высокоуровневости» промежуточного кода представления программы и полноты «метаинформации» о обрабатываемом коде, представленной (и переданной) каким-то образом
- Естественно, о многих оптимизациях, направленных на лучшее использование конкретных ресурсов процессора, часто приходится забыть (например, мультимедиа-обработка: только с помощью «родных» библиотек)
CIL: Метаинформация:
таблицы,
связи
Сама по себе обработка метаинформации уже является нетривиальной задачей
По сути, мы имеем структуру данных, типичную для нетривиальной базы данных
Например:
Одна из возможных реализаций CIL-процессора на базе DSP-ядра (2005, ННГУ, Wireless Lab)
- Стек отображается на регистровый файл с поддержкой автоматических выгрузок и загрузок регистрового файла в ОЗУ
- Поддерживаемые инструкции CIL отображаются на ресурсы DSP
- Метаинформация хранится в программно-управляемой кэш-памяти
- Типы данных проверяются динамически
- Неподдерживаемые CIL-инструкции исполняются как последовательности команд DSP (микропрограммно или путём вызова исключения)
Технология .NET и CIL
- Поддержка объектно-ориентированной идеологии
- Поддержка теневого стека, содержащего информацию о типах
- Ячейки памяти фактически имеют теги типов
- Boxing/unboxing команды (примитивные типы vs объекты)
- Команды создания, инициализации, загрузки, сохранения и копирования объектов, создание массивов объектов
- Загрузка и сохранение полей с контролем типов, вызов статических и виртуальных методов
- Приведение и поверка типов
- Автоматический сборщик мусора
- Операции с «токенами» (элементами метаинформации)
- Поддержка обработки исключений
SmallTalk: чистый объектный
Чистейший объектнейший язык программирования. Мечта всех объектно-ориентированных программистов.
- Язык «интеллектуальных» систем 5-го поколения
- Отсутствует понятие базовых типов
- С самого начала существовали намерения создания SmallTalk-процессора, но аппаратный процессор так и не был создан, программных реализацией было сделано очень много
- Разделены виртуальная машина и словарь (образ) – хранилище структур, данных и кода
- Существует большое количество систем (SmallTalk-80, Squeak, StrongTalk, …) с абсолютно разным набором байт-кодов
- Символьное связывание в ряде систем
- Достаточно тяжеловесные контексты методов, по сравнению с CIL модель исполнения выглядит очень тяжёлой
- Современные системы включают оптимизаторы байт-кода
- Рыночная ниша – высокоуровневые системы моделирования
SmallTalk: система команд
- Типы инструкций (StrongTalk от Sun)
- Доступ к локальным данным (аргументы метода, локальные переменные – загрузка и сохранение)
- Доступ к переменным экземпляра (полям)
- Доступ к переменным контекстов
- Доступ к переменным класса
- Доступ к глобальным переменным (словарю)
- Операторы передачи управления
- Посылка сообщений (в т.ч. себе и суперклассу, простых и полиморфных)
- Посылка сообщений-примитивов
- Зарезервированные сообщения-примитивы
- Вызовы и возвраты
- Плавающие операции
- SmallTalk-80 имеет более простой набор примитивов, адаптированный даже для 16-ти битовых машин Спасибо за внимание!