
Большинство мобильных устройств используют 64‑битные ARM‑процессоры. Однако они все заметнее и на серверах. Их число неуклонно растет, и все больше компаний, включая таких гигантов, как Amazon и Microsoft, также переходят на 64‑битные ARM.
У этих процессоров есть специальные инструкции — ARM NEON. Они обеспечивают параллелизм, известный как SIMD — Single Instruction, Multiple Data, то есть «Инструкция одна, данных множество». Например, можно сравнить шестнадцать одних значений с шестнадцатью других с помощью всего одной такой инструкции.
Некоторые из самых последних процессоров ARM также поддерживают еще более продвинутый набор команд — SVE, Scalable Vector Extension, или «Масштабируемое векторное расширение». Прогресс не останавливается — и вот уже появились спецификации SVE 2 и SVE 2.1.
Немного теории
Регистры NEON имеют четкое ограничение — размерность 128 бит. Регистры же SVE кратны 128 битам. Да, на практике они тоже чаще всего имеют размер 128 бит, как и NEON, но все же попадаются и исключения. Например, Amazon Graviton 3 основан на ядре ARM Neoverse V1, которое поддерживает SVE с длиной вектора 256 бит (32 байта). Что касается Graviton 4, он основан на ядре ARM Neoverse V2, которое поддерживает более развитый SVE2, правда с длиной вектора тоже 16 байт — как и «младший» NEON.
Кому по силам работать на таком low level, тому приходится выбирать между NEON и SVE.
Как отметил Ashton Six в комментарии к моей недавней статье, эти инструкции (NEON и SVE) можно смешивать и сочетать — в документации гарантируется, что первые 128 бит регистров SVE являются и регистрами NEON. Эштон продемонстрировал это на практическом примере кода на ассемблере.
Если компилятор C/C++ довольно свеж (хотя бы, GCC 14), можно легко переключаться между NEON и SVE. Если раздобыть заголовочный файл
arm_neon_sve_bridge.h
, достаточно описать две функции:svset_neonq
: устанавливает содержимое 128‑битного вектора NEON (uint8x16_t, int32x4_t и другие) в вектор SVE (svuint8_t, svint32_t… — ряд можно продолжить);svget_neonq
: извлекает первые 128 бит вектора SVE и возвращает их в виде 128‑битного вектора NEON.
Эти функции «бесплатны»: скорее всего, они компилируются вообще без инструкций.
Проверьте ARM-процессор в своих задачах.
Сервер с 128-ядерным процессором Ampere® Altra® Max M128-30 и 256 ГБ DDR4 уже в Selectel.
Переходим к практике
Позвольте проиллюстрировать на примере. В недавнем посте обсуждалось, что сложновато проверить, есть ли ненулевой байт в регистре NEON. Конкурентоспособное решение выглядит следующим образом:
int veq_non_zero_max(uint8x16_t v) {
return vmaxvq_u32(vreinterpretq_u32_u8(v)) != 0;
}
По сути, мы вычисляем в регистре максимальное 32‑битное целое, рассматривая его как четыре 32‑битных целых. Такая функция компилируется в три основные инструкции:
umaxv
, fmov
и cmp
.Рассмотрим альтернативу SVE. Происходит следующее:
- входные данные преобразуются в вектор SVE;
- создается маска для всех 16 позиций;
- каждый элемент сравнивается с нулем, чтобы сгенерировать предикат ненулевых позиций;
- проверяется, есть ли какие‑либо ненулевые элементы;
- наконец, возвращается 1, если да, и 0, если нет.
По сути, выполняется эффективная векторизованная проверка: «есть ли хоть один ненулевой»?
int sve_non_zero_max(uint8x16_t nvec) {
svuint8_t vec;
vec = svset_neonq_u8(vec, nvec);
svbool_t mask = svwhilelt_b8(0, 16);
svbool_t cmp = svcmpne_n_u8(mask, vec, 0);
return svptest_any(mask, cmp);
}
Если не считать инициализации маски, функция состоит из двух инструкций:
cmpne
и cset
. В некоторых ядрах ARM они могут быть объединены в одну. Несмотря на то, что код, смешивающий NEON и SVE, на первый взгляд выглядит сложнее, на деле он получается эффективнее!Если процессор, с которым вы работаете поддерживает SVE (SVE 2, SVE 2.1), и уже есть код для NEON, то можете попробовать добавить в него немного SVE.