Перейти к основному содержимому

Проект Java Valhalla

· 4 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

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;
}

./7bc6951c4b36f567c77f55ee763d3f47.svg

С другой стороны, код и соответствующая структура памяти типа значения Point будут такими:

value class Point {
int x;
int y
}

./0c69aec54f0dea1cd1b3364fcff7b453.svg

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

./5471b18083e2f3f3a50841b9ef9c7ad0.svg

С другой стороны, здесь мы видим соответствующую структуру памяти типа значения Point[] :

./bb90298cc0864883c8a4aae1f4e50c55.svg

Это также позволяет 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: