1. Обзор
В этой статье мы рассмотрим Project Valhalla — его исторические причины, текущее состояние разработки и то, что он дает повседневным разработчикам Java после его выпуска.
2. Мотивация и причины проекта Valhalla
В одном из своих выступлений Брайан Гетц, архитектор языка Java в Oracle, сказал, что одним из основных мотивов проекта Valhalla является желание адаптировать язык Java и среду выполнения к современному оборудованию . Когда был задуман язык Java (примерно 25 лет назад на момент написания), стоимость выборки памяти и арифметической операции была примерно одинаковой.
В настоящее время это изменилось, и операции выборки памяти стали от 200 до 1000 раз дороже, чем арифметические операции. С точки зрения дизайна языка это означает, что косвенные обращения, ведущие к выборке указателя, пагубно влияют на общую производительность.
Поскольку большинство структур данных Java в приложении являются объектами, мы можем рассматривать Java как язык с большим количеством указателей (хотя мы обычно не видим их и не манипулируем ими напрямую). Эта реализация объектов на основе указателей используется для обеспечения идентификации объекта, которая сама по себе используется для таких языковых функций, как полиморфизм, изменчивость и блокировка. Эти функции по умолчанию присутствуют в каждом объекте, независимо от того, нужны они на самом деле или нет.
Следуя цепочке идентичности, ведущей к указателям и указателям, ведущим к косвенным обращениям, причем косвенные действия имеют недостатки в производительности, логический вывод состоит в том, чтобы удалить их для структур данных, которые в них не нуждаются. Здесь в игру вступают типы значений.
3. Типы значений
Идея типов значений состоит в том, чтобы представлять чистые агрегаты данных . Это связано с отбрасыванием функций обычных объектов. Итак, у нас есть чистые данные, без идентификации. Это, конечно же, означает, что мы также теряем возможности, которые могли бы реализовать с помощью идентификатора объекта. Следовательно, сравнение на равенство может происходить только на основе состояния. Таким образом, мы не можем использовать репрезентативный полиморфизм и не можем использовать неизменяемые или необнуляемые объекты.
Поскольку у нас больше нет идентификатора объекта, мы можем отказаться от указателей и изменить общую структуру памяти для типов значений по сравнению с объектом. Давайте посмотрим на сравнение распределения памяти между классом Point
и соответствующим типом значения Point.
Код и соответствующая структура памяти обычного класса Point
будут такими:
final class Point {
final int x;
final int y;
}
С другой стороны, код и соответствующая структура памяти типа значения Point
будут такими:
value class Point {
int x;
int y
}
Это позволяет JVM объединять типы значений в массивы и объекты, а также в другие типы значений. На следующей диаграмме мы представляем отрицательный эффект косвенных обращений, когда мы используем класс Point в массиве:
С другой стороны, здесь мы видим соответствующую структуру памяти типа значения Point[]
:
Это также позволяет JVM передавать типы значений в стеке вместо того, чтобы выделять их в куче. В конце концов, это означает, что мы получаем агрегаты данных, поведение которых во время выполнения аналогично примитивам Java, таким как int
или float
.
Но в отличие от примитивов, типы значений могут иметь методы и поля. Мы также можем реализовать интерфейсы и использовать их как универсальные типы. Таким образом, мы можем взглянуть на типы значений с двух разных точек зрения:
- Более быстрые объекты
- Пользовательские примитивы
В качестве дополнительной вишенки на торте мы можем использовать типы значений в качестве универсальных типов без упаковки. Это напрямую подводит нас к другой большой функции Project Valhalla: специализированным дженерикам.
4. Специализированные дженерики
Когда мы хотим обобщить языковые примитивы, мы в настоящее время используем упакованные типы, такие как Integer
для int
или Float
для float
. Эта упаковка создает дополнительный уровень косвенности, тем самым сводя на нет цель использования примитивов для повышения производительности.
Поэтому мы видим множество специализированных специализаций для примитивных типов в существующих платформах и библиотеках, таких как IntStream<T>
или ToIntFunction<T>
. Это сделано для сохранения повышения производительности при использовании примитивов.
Итак, специализированные дженерики — это попытка устранить потребность в этих «хаках». Вместо этого язык Java стремится включить универсальные типы практически для всего: ссылок на объекты, примитивов, типов значений и, возможно, даже для void
.
5. Вывод
Мы рассмотрели изменения, которые Project Valhalla внесет в язык Java. Двумя основными целями являются повышение производительности и устранение дырявых абстракций.
Улучшения производительности достигаются за счет выравнивания графов объектов и удаления косвенных ссылок. Это приводит к более эффективному размещению памяти и меньшему количеству аллокаций и сборок мусора.
Лучшая абстракция приходит с примитивами и объектами, имеющими более похожее поведение при использовании в качестве универсальных типов.
Ранний прототип Project Valhalla, вводящий типы значений в существующую систему типов, имеет кодовое имя LW1 .
Мы можем найти больше информации о Project Valhalla на соответствующей странице проекта и JEP: