openjdk.ruOpenJDK на русском

JEP 417: Vector API (Third Incubator)

Vector API (третья версия Incubator (инкубационный модуль))

ОтветственныйPaul Sandoz
ТипFeature
ОбластьJDK
СтатусClosed / Delivered
Выпуск18
Компонентcore-libs
Обсуждениеpanama dash dev at openjdk dot java dot net
ТрудоёмкостьL
ДлительностьM
Связан сJEP 426: Vector API (Fourth Incubator)
JEP 414: Vector API (Second Incubator)
РецензентыJohn Rose, Vladimir Ivanov, Vladimir Kozlov
ОдобренJohn Rose, Vladimir Kozlov
Создан2021/06/24 16:44
Обновлён2023/05/12 15:35
Задача8269306

Аннотация

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

История

Vector API был впервые предложен в JEP 338 и включён в Java 16 как API в статусе Incubator. Второй этап в статусе Incubator был предложен в JEP 414 и включён в Java 17.

Здесь мы предлагаем внести улучшения с учётом отзывов, а также улучшения производительности и другие существенные улучшения реализации. Мы включаем следующие заметные изменения:

  • Поддержка платформы ARM Scalar Vector Extension (SVE).

  • Повышение производительности векторных операций, принимающих маски, на архитектурах с аппаратной поддержкой масок.

Цели

  • Ясный и лаконичный API — API должен позволять ясно и лаконично выражать широкий спектр векторных вычислений, состоящих из последовательностей векторных операций, скомпонованных внутри циклов и, возможно, с управляющей логикой (control flow). Должна быть возможность выразить вычисление, обобщённое относительно размера вектора, то есть числа линий (lanes) в векторе, чтобы такие вычисления были переносимы между оборудованием с разными размерами векторов.

  • Независимость от платформы — API должен быть независим от архитектуры CPU, чтобы его можно было реализовать на нескольких архитектурах, поддерживающих векторные инструкции. Как обычно в Java API, когда оптимизация под платформу и переносимость противоречат друг другу, предпочтение отдаётся переносимости API, даже если в результате некоторые платформенно-зависимые идиомы нельзя будет выразить в переносимом коде.

  • Надёжная компиляция во время выполнения и производительность на архитектурах x64 и AArch64 — На подходящих архитектурах x64 среда выполнения Java, а именно компилятор HotSpot C2, должна компилировать векторные операции в соответствующие эффективные и производительные векторные инструкции, например поддерживаемые Streaming SIMD Extensions (SSE) и Advanced Vector Extensions (AVX). Разработчики должны быть уверены, что выраженные ими векторные операции будут надёжно и близко отображаться на соответствующие векторные инструкции. Аналогично, на подходящих архитектурах ARM AArch64 C2 будет компилировать векторные операции в векторные инструкции, поддерживаемые NEON и SVE.

  • Плавная деградация — Иногда векторное вычисление невозможно во время выполнения полностью выразить в виде последовательности векторных инструкций, например потому, что архитектура не поддерживает некоторые из нужных инструкций. В таких случаях реализация Vector API должна плавно деградировать и продолжать работать. Это может включать выдачу предупреждений, если векторное вычисление не удаётся эффективно скомпилировать в векторные инструкции. На платформах без векторов плавная деградация даст код, сопоставимый по производительности с вручную развёрнутыми циклами, где коэффициент развёртки равен числу линий в выбранном векторе.

Что не является целью

  • Улучшение существующего алгоритма автовекторизации в HotSpot не является целью.

  • Поддержка векторных инструкций на архитектурах CPU, отличных от x64 и AArch64, не является целью. Однако важно отметить, как сказано в целях, что API не должен исключать такие реализации.

  • Поддержка компилятора C1 не является целью.

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

Мотивация

Векторное вычисление состоит из последовательности операций над векторами. Вектор представляет собой (обычно) фиксированную последовательность скалярных значений, где число скалярных значений соответствует числу аппаратно определённых линий вектора. Бинарная операция, применённая к двум векторам с одинаковым числом линий, для каждой линии применяет эквивалентную скалярную операцию к соответствующим двум скалярным значениям из каждого вектора. Обычно это называют Single Instruction Multiple Data (SIMD).

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

HotSpot уже поддерживает автовекторизацию, которая преобразует скалярные операции в суперсловные (superword) операции, которые затем отображаются на векторные инструкции. Набор преобразуемых скалярных операций ограничен и, кроме того, чувствителен к изменениям формы кода. Более того, может использоваться лишь часть доступных векторных инструкций, что ограничивает производительность сгенерированного кода.

Сегодня разработчику, который хочет писать скалярные операции, надёжно преобразуемые в суперсловные операции, нужно понимать алгоритм автовекторизации HotSpot и его ограничения, чтобы добиться надёжной и устойчивой производительности. В некоторых случаях написать преобразуемые скалярные операции может быть невозможно. Например, HotSpot не преобразует простые скалярные операции вычисления хэш-кода массива (отсюда методы Arrays::hashCode) и не может автовекторизовать код лексикографического сравнения двух массивов (поэтому мы добавили интринсик для лексикографического сравнения).

Vector API призван улучшить ситуацию, предоставив способ писать сложные векторные алгоритмы на Java с использованием существующего автовекторизатора HotSpot, но с пользовательской моделью, которая делает векторизацию гораздо более предсказуемой и надёжной. Написанные вручную векторные циклы могут выражать высокопроизводительные алгоритмы, например векторизованный hashCode или специализированные сравнения массивов, которые автовекторизатор может так никогда и не оптимизировать. Этот явный векторный API может быть полезен во многих областях, включая машинное обучение, линейную алгебру, криптографию, финансы и код внутри самого JDK.

Описание

Вектор представлен абстрактным классом Vector<E>. Переменная типа E конкретизируется упакованным (boxed) типом скалярных примитивных целочисленных типов или типов с плавающей точкой, элементы которых содержит вектор. У вектора также есть форма (shape), определяющая размер вектора в битах. Форма вектора определяет, как экземпляр Vector<E> отображается на аппаратный векторный регистр, когда векторные вычисления компилируются компилятором HotSpot C2. Длина вектора, то есть число линий или элементов, равна размеру вектора, делённому на размер элемента.

Поддерживаемый набор типов элементов (E): Byte, Short, Integer, Long, Float и Double, что соответствует скалярным примитивным типам byte, short, int, long, float и double соответственно.

Поддерживаемый набор форм соответствует размерам векторов 64, 128, 256 и 512 бит, а также max бит. Форма на 512 бит может упаковать значения byte в 64 линии или значения int в 16 линий, и вектор такой формы может обрабатывать 64 значения byte или 16 значений int за раз. Форма на max бит поддерживает максимальный размер вектора текущей архитектуры. Это позволяет поддержать платформу ARM SVE, реализации которой могут поддерживать любой фиксированный размер от 128 до 2048 бит с шагом 128 бит.

Мы считаем, что эти простые формы достаточно универсальны, чтобы быть полезными на всех актуальных платформах. Однако по мере экспериментов с будущими платформами, пока этот API находится в статусе Incubator, мы можем дополнительно изменить устройство параметра формы. Такая работа не входит в начальный объём этого проекта, но эти возможности отчасти определяют нынешнюю роль форм в Vector API. (Подробнее см. раздел Дальнейшая работа ниже.)

Сочетание типа элементов и формы определяет вид (species) вектора, представленный классом VectorSpecies<E>.

Операции над векторами делятся на поэлементные (lane-wise) и междорожечные (cross-lane).

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

  • Межлинейная (cross-lane) операция применяет операцию ко всему вектору целиком. Межлинейная операция возвращает либо скаляр, либо вектор, возможно, другой формы. Межлинейные операции далее делятся на операции перестановки и операции редукции.

Чтобы уменьшить поверхность API, мы определяем коллективные методы для каждого класса операций. Эти методы принимают на вход константы операторов; эти константы — экземпляры класса VectorOperator.Operator, определённые в статических final-полях класса VectorOperators. Для удобства мы определяем специальные методы, которые можно использовать вместо обобщённых, для некоторых распространённых полносервисных (full-service) операций, например сложения и умножения.

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

Класс Vector<E> объявляет набор методов для распространённых векторных операций, поддерживаемых всеми типами элементов. Для операций, специфичных для типа элементов, существует шесть абстрактных подклассов Vector<E>, по одному на каждый поддерживаемый тип элементов: ByteVector, ShortVector, IntVector, LongVector, FloatVector и DoubleVector. Эти подклассы для конкретных типов определяют дополнительные операции, привязанные к типу элементов, поскольку сигнатура метода ссылается либо на тип элементов, либо на соответствующий тип массива. Примеры таких операций — редукция (например, суммирование всех линий в скалярное значение) и копирование элементов вектора в массив. Эти подклассы также определяют дополнительные полносервисные операции, специфичные для целочисленных подтипов (например, побитовые операции, такие как логическое «или»), а также операции, специфичные для типов с плавающей точкой (например, трансцендентные математические функции, такие как возведение в степень).

С точки зрения реализации эти подклассы Vector<E> для конкретных типов дополнительно расширяются конкретными подклассами для разных форм векторов. Эти конкретные подклассы не являются публичными, поскольку нет необходимости предоставлять операции, специфичные для сочетаний типов и форм. Благодаря этому поверхность API сводится к сумме аспектов, а не к их произведению. Экземпляры конкретных классов Vector получают через фабричные методы, определённые в базовом классе Vector<E> и его подклассах для конкретных типов. Эти фабрики принимают на вход вид нужного экземпляра вектора и создают различные экземпляры, например экземпляр вектора, элементы которого имеют значения по умолчанию (то есть нулевой вектор), или экземпляр вектора, инициализированный из заданного массива.

Для поддержки управляющей логики некоторые векторные операции могут дополнительно принимать маски, представленные публичным абстрактным классом VectorMask<E>. Каждый элемент маски — логическое значение, соответствующее линии вектора. Маска выбирает линии, к которым применяется операция: она применяется, если элемент маски для линии равен true, и выполняется некоторое альтернативное действие, если он равен false.

Как и в случае векторов, экземпляры VectorMask<E> являются экземплярами непубличных конкретных подклассов, определённых для каждого сочетания типа элементов и длины. Экземпляр VectorMask<E>, используемый в операции, должен иметь тот же тип и ту же длину, что и экземпляры векторов, участвующие в операции. Операции сравнения векторов дают маски, которые затем можно передавать на вход другим операциям, чтобы выборочно работать с определёнными дорожками и тем самым эмулировать поток управления. Маски также можно создавать с помощью статических фабричных методов класса VectorMask<E>.

Мы ожидаем, что маски будут играть важную роль в разработке векторных вычислений, обобщённых относительно формы. Это ожидание основано на центральной роли предикатных регистров, эквивалента масок, в ARM Scalable Vector Extensions и в Intel AVX-512.

На таких платформах экземпляр VectorMask<E> отображается на предикатный регистр, а операция, принимающая маску, компилируется в векторную инструкцию, принимающую предикатный регистр. На платформах, которые не поддерживают предикатные регистры, применяется менее эффективный подход: экземпляр VectorMask<E> по возможности отображается на совместимый векторный регистр, и в общем случае операция, принимающая маску, составляется из эквивалентной операции без маски и операции смешивания (blend).

Для поддержки межлинейных операций перестановки некоторые векторные операции принимают перетасовки (shuffles), представленные публичным абстрактным классом VectorShuffle<E>. Каждый элемент перетасовки — значение int, соответствующее индексу линии. Перетасовка — это отображение индексов линий, описывающее перемещение элементов линий из заданного вектора в результирующий вектор.

Как и в случае векторов и масок, экземпляры VectorShuffle<E> являются экземплярами непубличных конкретных подклассов, определённых для каждого сочетания типа элементов и длины. Экземпляр VectorShuffle<E>, используемый в операции, должен иметь тот же тип и ту же длину, что и экземпляры векторов, участвующие в операции.

Пример

Вот простое скалярное вычисление над элементами массивов:

void scalarComputation(float[] a, float[] b, float[] c) {
   for (int i = 0; i < a.length; i++) {
        c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
   }
}

(Мы предполагаем, что массивы-аргументы имеют одинаковую длину.)

Вот эквивалентное векторное вычисление с использованием Vector API:

static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;

void vectorComputation(float[] a, float[] b, float[] c) {
    int i = 0;
    int upperBound = SPECIES.loopBound(a.length);
    for (; i < upperBound; i += SPECIES.length()) {
        // FloatVector va, vb, vc;
        var va = FloatVector.fromArray(SPECIES, a, i);
        var vb = FloatVector.fromArray(SPECIES, b, i);
        var vc = va.mul(va)
                   .add(vb.mul(vb))
                   .neg();
        vc.intoArray(c, i);
    }
    for (; i < a.length; i++) {
        c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
    }
}

Сначала мы получаем из FloatVector предпочтительный вид (species), форма которого оптимальна для текущей архитектуры. Мы храним его в поле static final, чтобы компилятор времени выполнения считал это значение константой и поэтому мог лучше оптимизировать векторные вычисления. Затем основной цикл проходит по входным массивам с шагом, равным длине вектора, то есть длине вида. Он загружает векторы float заданного вида из массивов a и b по соответствующему индексу, последовательно выполняет арифметические операции, а затем сохраняет результат в массив c. Если после последней итерации остаются элементы массива, то результаты для этих хвостовых элементов вычисляются обычным скалярным циклом.

Эта реализация обеспечивает оптимальную производительность на больших массивах. Компилятор HotSpot C2 генерирует машинный код, похожий на следующий, на процессоре Intel x64 с поддержкой AVX:

0.43%  / │  0x0000000113d43890: vmovdqu 0x10(%r8,%rbx,4),%ymm0
  7.38%  │ │  0x0000000113d43897: vmovdqu 0x10(%r10,%rbx,4),%ymm1
  8.70%  │ │  0x0000000113d4389e: vmulps %ymm0,%ymm0,%ymm0
  5.60%  │ │  0x0000000113d438a2: vmulps %ymm1,%ymm1,%ymm1
 13.16%  │ │  0x0000000113d438a6: vaddps %ymm0,%ymm1,%ymm0
 21.86%  │ │  0x0000000113d438aa: vxorps -0x7ad76b2(%rip),%ymm0,%ymm0
  7.66%  │ │  0x0000000113d438b2: vmovdqu %ymm0,0x10(%r9,%rbx,4)
 26.20%  │ │  0x0000000113d438b9: add    $0x8,%ebx
  6.44%  │ │  0x0000000113d438bc: cmp    %r11d,%ebx
         \ │  0x0000000113d438bf: jl     0x0000000113d43890

Это вывод микробенчмарка JMH для приведённого выше кода с использованием прототипа Vector API и реализации из ветки vectorIntrinsics репозитория разработки проекта Panama. Эти горячие участки сгенерированного машинного кода явно транслируются в векторные регистры и векторные инструкции. Мы отключили раскрутку циклов, чтобы трансляция была нагляднее; иначе HotSpot раскрутил бы этот код с помощью существующих оптимизаций циклов C2. Все выделения памяти под Java-объекты устранены.

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

void vectorComputation(float[] a, float[] b, float[] c) {
    for (int i = 0; i < a.length; i += SPECIES.length()) {
        // VectorMask<Float>  m;
        var m = SPECIES.indexInRange(i, a.length);
        // FloatVector va, vb, vc;
        var va = FloatVector.fromArray(SPECIES, a, i, m);
        var vb = FloatVector.fromArray(SPECIES, b, i, m);
        var vc = va.mul(va)
                   .add(vb.mul(vb))
                   .neg();
        vc.intoArray(c, i, m);
    }
}

В теле цикла мы получаем зависящую от цикла маску, которая передаётся операциям загрузки и сохранения. Когда i < SPECIES.loopBound(a.length), маска m объявляет все дорожки (lanes) установленными. На последней итерации цикла, когда SPECIES.loopBound(a.length) <= i < a.length и (a.length - i) <= SPECIES.length(), маска может объявить суффикс неустановленных дорожек. Операции загрузки и сохранения не будут выбрасывать исключения выхода за границы, поскольку маска не допускает обращения к массиву за пределами его длины.

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

Компиляция во время выполнения

У Vector API две реализации. Первая реализует операции на Java, поэтому она работоспособна, но не оптимальна. Вторая определяет встроенные (intrinsic) векторные операции для компилятора времени выполнения HotSpot C2, чтобы он мог компилировать векторные вычисления в соответствующие аппаратные регистры и векторные инструкции, если они доступны.

Чтобы избежать взрывного роста числа intrinsic-функций C2, мы определяем обобщённые intrinsic-функции, соответствующие различным видам операций, таким как унарные, бинарные, преобразования и так далее, которые принимают параметр, описывающий конкретную выполняемую операцию. Около двадцати новых intrinsic-функций обеспечивают поддержку всего API.

Мы рассчитываем в конечном итоге объявить векторные классы как primitive-классы, как предложено проектом Valhalla в JEP 401 (Primitive Objects). Пока же Vector<E> и его подклассы считаются value-based классами, поэтому операций, зависящих от идентичности, над их экземплярами следует избегать. Хотя экземпляры векторов абстрактно состоят из элементов в дорожках, C2 не скаляризует эти элементы — значение вектора рассматривается как единое целое, подобно int или long, и отображается на векторный регистр соответствующего размера. C2 обрабатывает экземпляры векторов особым образом, чтобы обойти ограничения escape-анализа и избежать упаковки (boxing).

Intrinsic-функции Intel SVML для трансцендентных операций

Vector API поддерживает трансцендентные и тригонометрические поэлементные (lanewise) операции над векторами с плавающей точкой. На x64 мы используем Intel Short Vector Math Library (SVML), чтобы предоставить оптимизированные intrinsic-реализации таких операций. Intrinsic-операции обладают теми же численными свойствами, что и соответствующие скалярные операции, определённые в java.lang.Math.

Ассемблерные исходные файлы для операций SVML находятся в исходном коде модуля jdk.incubator.vector, в каталогах для конкретных ОС. Процесс сборки JDK компилирует эти исходные файлы для целевой операционной системы в разделяемую библиотеку SVML. Эта библиотека довольно велика: её размер чуть меньше мегабайта. Если образ JDK, собранный с помощью jlink, не включает модуль jdk.incubator.vector, то библиотека SVML не будет скопирована в образ.

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

Среда выполнения HotSpot попытается загрузить библиотеку SVML и, если она есть, привяжет операции из библиотеки SVML к именованным процедурам-заглушкам. Компилятор C2 генерирует код, вызывающий соответствующую процедуру-заглушку в зависимости от операции и вида вектора (то есть типа элементов и формы).

Если в будущем проект Panama расширит поддержку нативных соглашений о вызовах так, чтобы поддерживать векторные значения, то реализация Vector API, возможно, сможет загружать библиотеку SVML из внешнего источника. Если такой подход не скажется на производительности, то больше не нужно будет включать SVML в виде исходного кода и собирать её в составе JDK. До тех пор мы считаем описанный выше подход приемлемым, учитывая возможный выигрыш в производительности.

Дальнейшая работа

  • Как упомянуто выше, мы рассчитываем в конечном итоге объявить векторные классы как primitive-классы. Кроме того, мы рассчитываем использовать обобщённую специализацию (generic specialization) primitive-классов из проекта Valhalla, чтобы экземпляры Vector<E> могли быть примитивными значениями, конкретные типы которых являются примитивными типами. Так будет проще оптимизировать и выражать векторные вычисления. Подтипы Vector<E> для конкретных типов, такие как IntVector, могут оказаться ненужными, когда появится обобщённая специализация primitive-классов. Мы намерены развивать API в статусе Incubator на протяжении нескольких выпусков и адаптировать его по мере появления primitive-классов и связанных с ними возможностей.

  • Мы намерены улучшить API, чтобы загружать и сохранять векторы с помощью JEP 412 (Foreign Function & Memory API), когда этот API выйдет из статуса Incubator. Раскладки памяти, описывающие виды векторов, могут оказаться полезными, например, для прохода с шагом по сегменту памяти, состоящему из векторных элементов.

  • Мы рассчитываем улучшить реализацию, чтобы лучше оптимизировать циклы, содержащие векторизованный код, и в целом постепенно повышать производительность со временем.

  • Мы также рассчитываем улучшить комбинаторные модульные тесты, чтобы они проверяли, что C2 генерирует аппаратные векторные инструкции. Сейчас модульные тесты без проверки предполагают, что аппаратные векторные инструкции будут сгенерированы при достаточном числе повторных выполнений. Мы изучим использование IR Test Framework компилятора C2, чтобы кросс-платформенно проверять наличие векторных узлов в графе IR (например, с помощью сопоставления с регулярными выражениями). Если этот подход окажется проблематичным, мы можем изучить более простой подход с использованием флага -XX:+TraceNewVectors, доступного только в непродуктовых сборках, для вывода векторных узлов.

Альтернативы

Альтернативный подход — автовекторизация в HotSpot, но она потребовала бы значительной работы. Более того, по сравнению с Vector API она всё равно была бы хрупкой и ограниченной, поскольку автовекторизацию при сложном потоке управления выполнить очень трудно.

В целом, даже после десятилетий исследований — особенно для циклов по массивам в FORTRAN и C — автовекторизация скалярного кода, по-видимому, не является надёжной тактикой оптимизации произвольных циклов, написанных пользователем, если только пользователь не уделяет необычайно пристального внимания неписаным соглашениям о том, какие именно циклы компилятор готов автовекторизовать. Слишком легко написать цикл, который не удаётся автовекторизовать по причине, которую ни один человек, читающий код, не может обнаружить. Годы работы над автовекторизацией, даже в HotSpot, оставили нам множество механизмов оптимизации, которые срабатывают лишь в особых случаях. Мы хотим пользоваться этими механизмами чаще!

Тестирование

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

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

Риски и допущения

  • Существует риск, что API будет смещён в сторону функциональности SIMD, поддерживаемой на архитектурах x64, но этот риск снижается благодаря поддержке AArch64. В основном это относится к явно фиксированному набору поддерживаемых форм, который мешает писать алгоритмы в виде, не зависящем от формы. Большинство остальных операций Vector API, по нашему мнению, склоняют к переносимым алгоритмам. Чтобы снизить этот риск, мы будем учитывать другие архитектуры, в частности архитектуру ARM Scalar Vector Extension, модель программирования которой динамически подстраивается под единственную фиксированную форму, поддерживаемую оборудованием. Мы приветствуем и поощряем участие в этой работе участников OpenJDK, работающих над ARM-специфичными частями HotSpot.

  • Vector API использует типы-обёртки (например, Integer) как заместители примитивных типов (например, int). Это решение вынужденное и продиктовано текущими ограничениями обобщённых типов Java, которые плохо приспособлены к примитивным типам. Когда проект Valhalla в конечном итоге добавит более мощные обобщённые типы, текущее решение будет выглядеть неудобным и, скорее всего, его придётся изменить. Мы предполагаем, что такие изменения будут возможны без чрезмерного нарушения обратной совместимости.