stringtranslate.com

Векторный процессор

В вычислительной технике векторный процессор или процессор массива — это центральный процессор (ЦП), реализующий набор инструкций , где его инструкции разработаны для эффективной и действенной работы с большими одномерными массивами данных, называемыми векторами . Это контрастирует со скалярными процессорами , инструкции которых работают только с отдельными элементами данных, и контрастирует с некоторыми из тех же скалярных процессоров, имеющими дополнительные арифметические единицы с одной инструкцией, несколькими данными (SIMD) или SIMD в регистре (SWAR). Векторные процессоры могут значительно повысить производительность при определенных рабочих нагрузках, в частности, при численном моделировании и подобных задачах. Методы векторной обработки также работают в оборудовании игровых консолей и в графических ускорителях .

Векторные машины появились в начале 1970-х и доминировали в разработке суперкомпьютеров с 1970-х по 1990-е годы, особенно на различных платформах Cray . Быстрое падение соотношения цены и производительности традиционных микропроцессорных конструкций привело к снижению популярности векторных суперкомпьютеров в 1990-х годах.

История

Ранние исследования и разработки

Разработка векторной обработки началась в начале 1960-х годов в Westinghouse Electric Corporation в проекте Solomon . Целью Solomon было значительное увеличение производительности математических вычислений за счет использования большого количества простых сопроцессоров под управлением одного главного центрального процессора (ЦП). ЦП подавал одну общую инструкцию всем арифметико-логическим устройствам (АЛУ), по одной на цикл, но с разными точками данных для каждого из них. Это позволяло машине Solomon применять один алгоритм к большому набору данных , подаваемому в виде массива. [ необходима цитата ]

В 1962 году Westinghouse отменил проект, но усилия были возобновлены Университетом Иллинойса в Урбане-Шампейне как ILLIAC IV . Их версия проекта изначально требовала машины производительностью 1 GFLOPS с 256 ALU, но, когда она наконец была доставлена ​​в 1972 году, у нее было всего 64 ALU и она могла достигать только 100–150 MFLOPS. Тем не менее, она показала, что базовая концепция была разумной, и при использовании в приложениях с интенсивным использованием данных, таких как вычислительная гидродинамика , ILLIAC была самой быстрой машиной в мире. Подход ILLIAC с использованием отдельных ALU для каждого элемента данных не является общим для более поздних разработок и часто упоминается в отдельной категории, массивно параллельные вычисления. Примерно в это же время Флинн классифицировал этот тип обработки как раннюю форму одной инструкции, нескольких потоков (SIMT). [ необходима цитата ]

Компания International Computers Limited стремилась избежать многих трудностей, связанных с концепцией ILLIAC, с помощью собственной разработки процессора распределенной матрицы (DAP), классифицируя ILLIAC и DAP как процессоры клеточной матрицы, которые потенциально обеспечивали существенные преимущества в производительности по сравнению с традиционными векторными процессорами, такими как CDC STAR-100 и Cray 1. [1]

Компьютер для работы с функциями

В 1967 году Карцевым была представлена ​​и разработана ЭВМ для операций с функциями. [2]

Суперкомпьютеры

Первыми векторными суперкомпьютерами стали Control Data Corporation STAR-100 и Texas Instruments Advanced Scientific Computer (ASC), которые были представлены в 1974 и 1972 годах соответственно.

Базовый ASC (т. е. "однотрубный") ALU использовал архитектуру конвейера, которая поддерживала как скалярные, так и векторные вычисления, с пиковой производительностью, достигающей приблизительно 20 MFLOPS, легко достигаемой при обработке длинных векторов. Расширенные конфигурации ALU поддерживали "два трубопровода" или "четыре трубопровода" с соответствующим 2X или 4X приростом производительности. Пропускная способность памяти была достаточной для поддержки этих расширенных режимов.

В остальном STAR-100 был медленнее, чем собственные суперкомпьютеры CDC, такие как CDC 7600 , но в задачах, связанных с данными, они могли справляться с ними, будучи намного меньше и менее дорогими. Однако машине также требовалось значительное время для декодирования векторных инструкций и подготовки к запуску процесса, поэтому ей требовалось работать с очень конкретными наборами данных, прежде чем она действительно что-то ускорила.

Векторная техника была впервые полностью использована в 1976 году знаменитым Cray-1 . Вместо того, чтобы оставлять данные в памяти, как STAR-100 и ASC, конструкция Cray имела восемь векторных регистров , которые содержали шестьдесят четыре 64-битных слова каждый. Векторные инструкции применялись между регистрами, что намного быстрее, чем обращение к основной памяти. В то время как STAR-100 применял одну операцию к длинному вектору в памяти, а затем переходил к следующей операции, конструкция Cray загружала меньшую часть вектора в регистры, а затем применяла к этим данным столько операций, сколько могла, тем самым избегая многих гораздо более медленных операций доступа к памяти.

В конструкции Cray использовался конвейерный параллелизм для реализации векторных инструкций вместо нескольких АЛУ. Кроме того, конструкция имела полностью отдельные конвейеры для разных инструкций, например, сложение/вычитание было реализовано на другом оборудовании, чем умножение. Это позволяло передавать пакет векторных инструкций по конвейеру в каждый из подблоков АЛУ, метод, который они назвали векторной цепочкой . Cray-1 обычно имел производительность около 80 MFLOPS, но при работе до трех цепочек он мог достигать пика в 240 MFLOPS и в среднем около 150 — намного быстрее, чем любая машина той эпохи.

Процессорный модуль Cray J90 с четырьмя скалярными/векторными процессорами

Затем последовали и другие примеры. Control Data Corporation попыталась снова выйти на рынок high-end со своей машиной ETA-10 , но она плохо продавалась, и они воспользовались этим как возможностью полностью покинуть сферу суперкомпьютеров. В начале и середине 1980-х годов японские компании ( Fujitsu , Hitachi и Nippon Electric Corporation (NEC) представили векторные машины на основе регистров, похожие на Cray-1, которые обычно были немного быстрее и намного меньше. Базирующиеся в Орегоне Floating Point Systems (FPS) построили дополнительные процессоры массивов для мини-компьютеров , а затем создали свои собственные мини-суперкомпьютеры .

На протяжении всего этого времени Cray продолжала оставаться лидером по производительности, постоянно побеждая конкурентов с серией машин, которые привели к Cray-2 , Cray X-MP и Cray Y-MP . С тех пор рынок суперкомпьютеров был сосредоточен больше на массивно-параллельной обработке, чем на более совершенных реализациях векторных процессоров. Однако, осознав преимущества векторной обработки, IBM разработала виртуальную векторную архитектуру для использования в суперкомпьютерах, объединяющих несколько скалярных процессоров для работы в качестве векторного процессора.

Хотя векторные суперкомпьютеры, похожие на Cray-1, в наши дни менее популярны, NEC продолжает выпускать этот тип компьютеров вплоть до сегодняшнего дня с помощью серии компьютеров SX . Совсем недавно, SX-Aurora TSUBASA размещает процессор и либо 24, либо 48 гигабайт памяти на модуле HBM 2 внутри платы, которая физически напоминает графический сопроцессор, но вместо того, чтобы служить сопроцессором, он является основным компьютером с совместимым с ПК компьютером, к которому он подключен, выполняющим функции поддержки.

ГПУ

Современные графические процессоры ( GPU ) включают в себя массив шейдерных конвейеров , которые могут управляться вычислительными ядрами , и могут считаться векторными процессорами (использующими похожую стратегию для сокрытия задержек памяти). Как показано в статье Флинна 1972 года, ключевым отличительным фактором графических процессоров на основе SIMT является то, что он имеет один декодер-транслятор инструкций, но ядра, получающие и выполняющие эту же инструкцию, в остальном являются достаточно обычными: их собственные АЛУ, их собственные файлы регистров, их собственные блоки загрузки/сохранения и их собственные независимые кэши данных L1. Таким образом, хотя все ядра одновременно выполняют одну и ту же инструкцию в синхронном режиме друг с другом, они делают это с совершенно разными данными из совершенно разных областей памяти. Это значительно сложнее и запутаннее, чем «упакованный SIMD» , который строго ограничен выполнением только параллельных конвейерных арифметических операций. Хотя точные внутренние детали современных коммерческих графических процессоров являются конфиденциальной информацией, команда MIAOW [3] смогла собрать воедино разрозненную информацию, достаточную для реализации подмножества архитектуры AMDGPU. [4]

Недавнее развитие

Несколько современных архитектур ЦП разрабатываются как векторные процессоры. Расширение вектора RISC-V следует тем же принципам, что и ранние векторные процессоры, и реализуется в коммерческих продуктах, таких как Andes Technology AX45MPV. [5] Также разрабатывается несколько архитектур векторных процессоров с открытым исходным кодом , включая ForwardCom и Libre-SOC .

Сравнение с современной архитектурой

По состоянию на 2016 год большинство массовых ЦП реализуют архитектуры, в которых используются инструкции SIMD фиксированной длины. На первый взгляд их можно считать формой векторной обработки, поскольку они работают с несколькими (векторизованными, явной длины) наборами данных и заимствуют функции из векторных процессоров. Однако по определению добавление SIMD само по себе не может квалифицировать процессор как фактический векторный процессор , поскольку SIMD имеет фиксированную длину , а векторы — переменную длину . Разница проиллюстрирована ниже на примерах, показывающих и сравнивающих три категории: чистый SIMD, предицированный SIMD и чистая векторная обработка. [ требуется ссылка ]

Другие конструкции ЦП включают несколько инструкций для векторной обработки на нескольких (векторизованных) наборах данных, обычно известных как MIMD (множественные инструкции, множественные данные) и реализованных с помощью VLIW (очень длинное слово инструкции) и EPIC (явно параллельные вычисления инструкций). Процессор Fujitsu FR-V VLIW/вектор объединяет обе технологии.

Разница между SIMD и векторными процессорами

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

В то время как чистый (фиксированной ширины, без предикации) SIMD часто ошибочно называют «векторным» (потому что SIMD обрабатывает данные, которые являются векторами), путем тщательного анализа и сравнения исторических и современных ISA можно заметить, что фактические векторные ISA обладают следующими характеристиками, которых нет ни в одной SIMD ISA: [ необходима ссылка ]

Предикативный SIMD (часть таксономии Флинна ), который представляет собой комплексные индивидуальные маски предикатов на уровне элементов для каждой векторной инструкции, как теперь доступно в ARM SVE2. [9] И AVX-512 , почти квалифицируется как векторный процессор. [ как? ] Предикативный SIMD использует SIMD ALU фиксированной ширины, но допускает локально управляемую (предиктивную) активацию блоков для обеспечения видимости векторов переменной длины. Примеры ниже помогают объяснить эти категориальные различия.

SIMD, поскольку он использует пакетную обработку фиксированной ширины, по своей сути не способен справиться с итерацией и сокращением. Это проиллюстрировано далее примерами ниже.

Кроме того, векторные процессоры могут быть более ресурсоэффективными за счет использования более медленного оборудования и экономии энергии, но при этом по-прежнему достигать пропускной способности и иметь меньшую задержку, чем SIMD, за счет векторной цепочки . [10] [11]

Рассмотрим как SIMD-процессор, так и векторный процессор, работающие на 4 64-битных элементах, выполняющие последовательность ЗАГРУЗКИ, СЛОЖЕНИЯ, УМНОЖЕНИЯ и СОХРАНЕНИЯ. Если ширина SIMD равна 4, то SIMD-процессор должен ЗАГРУЗИТЬ четыре элемента полностью, прежде чем он сможет перейти к СЛОЖЕНИЯМ, должен завершить все СЛОЖЕНИЯ, прежде чем он сможет перейти к УМНОЖЕНИЯМ, и также должен завершить все УМНОЖЕНИЯ, прежде чем он сможет начать СОХРАНЕНИЯ. Это по определению и по замыслу. [12]

Необходимость выполнять 4-широкие одновременные 64-битные LOAD и 64-битные STORE очень затратна с точки зрения оборудования (256-битные пути данных к памяти). Наличие 4x 64-битных ALU, особенно MULTIPLY, аналогично. Чтобы избежать этих высоких затрат, процессор SIMD должен иметь 1-широкую 64-битную LOAD, 1-широкую 64-битную STORE и только 2-широкие 64-битные ALU. Как показано на схеме, которая предполагает модель выполнения с несколькими выпусками , последствия таковы, что операции теперь выполняются дольше. Если многовыпуск невозможен, то операции выполняются еще дольше, потому что LD может не быть выпущен (запущен) одновременно с первыми ADD, и так далее. Если имеется только 4 64-битных SIMD ALU, время завершения еще больше: только после завершения всех четырех операций LOAD могут начаться операции SIMD, и только после завершения всех операций ALU могут начаться операции STORE.

Векторный процессор, напротив, даже если он однозадачный и не использует SIMD ALU, имея только 1-wide 64-битную ЗАГРУЗКУ, 1-wide 64-битную СОХРАНЕНИЕ (и, как в Cray-1 , возможность одновременного выполнения УМНОЖЕНИЯ с СЛОЖЕНИЕМ), может завершить четыре операции быстрее, чем SIMD-процессор с 1-wide ЗАГРУЗКОЙ, 1-wide СОХРАНЕНИЕМ и 2-wide SIMD. Это более эффективное использование ресурсов, благодаря векторному сцеплению , является ключевым преимуществом и отличием по сравнению с SIMD. SIMD, по замыслу и определению, не может выполнять сцепление, кроме как для всей группы результатов. [13]

Описание

В общих чертах, ЦП способны манипулировать одним или двумя фрагментами данных одновременно. Например, большинство ЦП имеют инструкцию, которая по сути говорит «сложить A с B и поместить результат в C». Данные для A, B и C могут быть — по крайней мере, в теории — закодированы непосредственно в инструкции. Однако в эффективной реализации все редко бывает так просто. Данные редко отправляются в необработанном виде, а вместо этого «указываются» путем передачи адреса в область памяти, в которой хранятся данные. Декодирование этого адреса и извлечение данных из памяти занимает некоторое время, в течение которого ЦП традиционно простаивает, ожидая появления запрошенных данных. По мере увеличения скорости ЦП эта задержка памяти исторически стала большим препятствием для производительности; см. Оперативная память § Стена памяти .

Чтобы сократить время, затрачиваемое на эти шаги, большинство современных ЦП используют технику, известную как конвейеризация инструкций , при которой инструкции поочередно проходят через несколько подблоков. Первый подблок считывает адрес и декодирует его, следующий «извлекает» значения по этим адресам, а следующий выполняет саму математику. С конвейеризацией «трюк» заключается в том, чтобы начать декодирование следующей инструкции еще до того, как первая покинет ЦП, по принципу сборочной линии , поэтому декодер адреса постоянно используется. Любая конкретная инструкция занимает одинаковое количество времени для завершения, время, известное как задержка , но ЦП может обрабатывать целую партию операций, перекрываясь, гораздо быстрее и эффективнее, чем если бы он делал это по одной за раз.

Векторные процессоры продвигают эту концепцию на шаг дальше. Вместо конвейеризации только инструкций они также конвейеризуют сами данные. Процессору подаются инструкции, которые говорят не просто сложить A с B, но и сложить все числа «отсюда досюда» со всеми числами «отсюда досюда». Вместо того, чтобы постоянно декодировать инструкции и затем извлекать данные, необходимые для их завершения, процессор считывает одну инструкцию из памяти, и в определении самой инструкции просто подразумевается, что инструкция будет работать снова с другим элементом данных, по адресу на один шаг больше, чем предыдущий. Это позволяет значительно сэкономить время декодирования.

Чтобы проиллюстрировать, какое это может иметь значение, рассмотрим простую задачу сложения двух групп по 10 чисел. На обычном языке программирования можно было бы написать «цикл», который по очереди выбирал бы каждую из пар чисел, а затем складывал бы их. Для ЦП это выглядело бы примерно так:

; Гипотетическая машина RISC ; предположим, что a, b и c — ячейки памяти в соответствующих регистрах ; сложить 10 чисел из a с 10 числами из b, сохранить результат в c move $10 , count ; count := 10 loop: load r1 , a load r2 , b add r3 , r1 , r2 ; r3 := r1 + r2 store r3 , c add a , a , $4 ; move next add b , b , $4 add c , c , $4 dec count ; уменьшить jnez count , loop ; вернуться назад, если count еще не равен 0 ret                                       

Но для векторного процессора эта задача выглядит совершенно иначе:

; предположим, что у нас есть векторные регистры v1-v3 ; с размером, равным или большим 10 move $10 , count ; count = 10 vload v1 , a , count vload v2 , b , count vadd v3 , v1 , v2 vstore v3 , c , count ret                     

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

Векторные ISA в стиле Cray идут на шаг дальше и предоставляют глобальный регистр «счетчика», называемый длиной вектора (VL):

; снова предположим, что у нас есть векторные регистры v1-v3 ; с размером больше или равным 10 setvli $10 # Устанавливаем длину вектора VL=10 vload v1 , a # 10 загружает из a vload v2 , b # 10 загружает из b vadd v3 , v1 , v2 # 10 добавляет vstore v3 , c # 10 сохраняет в c ret                     

Такой подход обеспечивает ряд преимуществ экономии. [14]

  1. Требуется только три трансляции адресов. В зависимости от архитектуры это может представлять значительную экономию само по себе.
  2. Еще одним способом экономии является извлечение и декодирование самой инструкции, что необходимо сделать только один раз вместо десяти.
  3. Сам код также стал меньше, что может привести к более эффективному использованию памяти, уменьшению размера кэша инструкций L1 и снижению энергопотребления.
  4. С уменьшением размера программы предсказание ветвлений становится проще.
  5. Поскольку длина (эквивалентная ширине SIMD) не задается жестко в инструкции, кодирование не только более компактно, но и «защищено от будущих изменений» и позволяет даже в проектах встраиваемых процессоров рассматривать возможность использования векторов исключительно для получения всех остальных преимуществ, а не для достижения высокой производительности.

Кроме того, в более современных векторных процессорах ISA введена функция «Fail on First» или «Fault First» (см. ниже), которая дает еще больше преимуществ.

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

Не все проблемы можно решить с помощью такого рода решения. Включение этих типов инструкций обязательно добавляет сложности ядру ЦП. Эта сложность обычно заставляет другие инструкции работать медленнее, например, когда он не складывает много чисел подряд. Более сложные инструкции также увеличивают сложность декодеров, что может замедлить декодирование более распространенных инструкций, таких как обычное сложение. ( Это можно несколько смягчить, сохранив все принципы ISA в RISC : RVV добавляет только около 190 векторных инструкций даже с расширенными функциями. [15] )

Векторные процессоры традиционно проектировались для лучшей работы только при наличии больших объемов данных для обработки. По этой причине такие типы ЦП были обнаружены в основном в суперкомпьютерах , поскольку сами суперкомпьютеры, в общем, находились в таких местах, как центры прогнозирования погоды и физические лаборатории, где «обрабатываются» огромные объемы данных. Однако, как показано выше и продемонстрировано RISC-V RVV, эффективность векторных ISA дает другие преимущества, которые являются убедительными даже для встроенных вариантов использования.

Векторные инструкции

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

Самоповторяющиеся инструкции встречаются в ранних векторных компьютерах, таких как STAR-100, где вышеуказанное действие описывалось одной инструкцией (что-то вроде vadd c, a, b, $10). Они также встречаются в архитектуре x86 в качестве REPпрефикса. Однако только очень простые вычисления могут быть эффективно выполнены на оборудовании таким образом без очень большого увеличения стоимости. Поскольку все операнды должны находиться в памяти для архитектуры STAR-100, задержка, вызванная доступом, также стала огромной.

Интересно, однако, что Broadcom включил пространство во все векторные операции Videocore IV ISA для REPполя, но в отличие от STAR-100, который использует память для своих повторов, повторы Videocore IV есть во всех операциях, включая арифметические векторные операции. Длина повтора может быть небольшим диапазоном степени двойки или получена из одного из скалярных регистров. [16]

Cray -1 представил идею использования регистров процессора для хранения векторных данных в пакетах. Длина пакета (длина вектора, VL) могла быть динамически установлена ​​с помощью специальной инструкции, значимость по сравнению с Videocore IV (и, что особенно важно, как будет показано ниже, SIMD) заключается в том, что длина повтора не обязательно должна быть частью кодирования инструкции. Таким образом, в каждом пакете можно выполнить значительно больше работы; кодирование инструкции также намного более элегантно и компактно. Единственный недостаток заключается в том, что для того, чтобы в полной мере воспользоваться этой дополнительной емкостью пакетной обработки, загрузка памяти и скорость хранения соответственно также должны были увеличиться. Иногда это утверждается [ кем? ] как недостаток векторных процессоров в стиле Cray: на самом деле это часть достижения высокой производительности пропускной способности, как это видно в графических процессорах , которые сталкиваются с точно такой же проблемой.

Современные компьютеры SIMD, как утверждается, улучшают ранний Cray, напрямую используя несколько АЛУ, для более высокой степени параллелизма по сравнению с использованием только обычного скалярного конвейера. Современные векторные процессоры (такие как SX-Aurora TSUBASA ) объединяют оба, выдавая несколько данных нескольким внутренним конвейерным АЛУ SIMD, причем число выданных данных динамически выбирается векторной программой во время выполнения. Маски могут использоваться для выборочной загрузки и хранения данных в ячейках памяти, а также использовать те же маски для выборочного отключения элемента обработки АЛУ SIMD. Некоторые процессоры с SIMD ( AVX-512 , ARM SVE2 ) способны к такому виду выборочной, поэлементной ( «предиктивной» ) обработки, и именно они в некоторой степени заслуживают номенклатуры «векторный процессор» или, по крайней мере, заслуживают утверждения о способности к «векторной обработке». Процессоры SIMD без предикации поэлементно ( MMX , SSE , AltiVec ) категорически этого не делают.

Современные графические процессоры, которые имеют множество небольших вычислительных блоков, каждый из которых имеет собственный независимый SIMD ALU, используют Single Instruction Multiple Threads (SIMT). Блоки SIMT работают из общего единого широковещательного синхронизированного блока инструкций. «Векторные регистры» очень широкие, а конвейеры, как правило, длинные. Часть SIMT «потоки» включает способ обработки данных независимо на каждом из вычислительных блоков.

Кроме того, графические процессоры, такие как Broadcom Videocore IV и другие внешние векторные процессоры, такие как NEC SX-Aurora TSUBASA, могут использовать меньше векторных единиц, чем подразумевает ширина: вместо того, чтобы иметь 64 единицы для 64-числового регистра, оборудование может вместо этого выполнять конвейерный цикл по 16 единицам для гибридного подхода. Broadcom Videocore IV также способен на этот гибридный подход: номинально заявляя, что его SIMD QPU Engine поддерживает операции с массивами FP длиной 16 в своих инструкциях, он фактически делает их по 4 за раз, как (другую) форму «потоков». [17]

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

Этот пример начинается с алгоритма ("IAXPY"), сначала показываем его в скалярных инструкциях, затем SIMD, затем предицированных SIMD и, наконец, векторных инструкциях. Это постепенно помогает проиллюстрировать разницу между традиционным векторным процессором и современным SIMD. Пример начинается с 32-битного целочисленного варианта функции "DAXPY" на языке C :

void iaxpy ( size_t n , int a , const int x [], int y []) { for ( size_t i = 0 ; i < n ; i ++ ) y [ i ] = a * x [ i ] + y [ i ]; }                          

В каждой итерации каждый элемент y имеет элемент x, умноженный на a и добавленный к нему. Программа выражена в скалярной линейной форме для удобства чтения.

Скалярный ассемблер

Скалярная версия этого кода загружает по одному значению x и y, выполняет одно вычисление, сохраняет один результат и выполняет цикл:

loop: load32 r1 , x ; загрузить одни 32-битные данные load32 r2 , y mul32 r1 , a , r1 ; r1 := r1 * a add32 r3 , r1 , r2 ; r3 := r1 + r2 store32 r3 , y addl x , x , $4 ; x := x + 4 addl y , y , $4 subl n , n , $1 ; n := n - 1 jgz n , loop ; вернуться назад, если n > 0 out: ret                                       

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

 ; Предположим, что tmp предварительно выделен vmul tmp , a , x , n ; tmp[i] = a * x[i] vadd y , y , tmp , n ; y[i] = y[i] + tmp[i] ret             

Чистый (непредикативный, упакованный) SIMD

Современная упакованная архитектура SIMD, известная под многими названиями (перечисленными в таксономии Флинна ), может выполнять большую часть операций в пакетах. Код в основном похож на скалярную версию. Предполагается, что и x, и y здесь правильно выровнены (только начинаются с кратного 16) и что n кратно 4, так как в противном случае потребовался бы некоторый код настройки для вычисления маски или запуска скалярной версии. Также можно предположить, для простоты, что инструкции SIMD имеют возможность автоматически повторять скалярные операнды, как это может делать ARM NEON. [18] Если это не так, необходимо использовать «сплат» (широковещательную передачу) для копирования скалярного аргумента через регистр SIMD:

 splatx4 v4 , а ; v4 = а,а,а,а   

Затраченное время будет в принципе таким же, как и при векторной реализации, y = mx + cописанной выше.

vloop: load32x4 v1 , x load32x4 v2 , y mul32x4 v1 , a , v1 ; v1 := v1 * a add32x4 v3 , v1 , v2 ; v3 := v1 + v2 store32x4 v3 , y addl x , x , $16 ; x := x + 16 addl y , y , $16 subl n , n , $4 ; n := n - 4 jgz n , vloop ; вернуться назад, если n > 0 out: ret                                      

Обратите внимание, что оба указателя x и y увеличиваются на 16, поскольку именно столько (в байтах) составляют четыре 32-битных целых числа. Было принято решение, что алгоритм должен справляться только с 4-ширинным SIMD, поэтому константа жестко закодирована в программе.

К сожалению для SIMD, ключ к разгадке кроется в предположении выше, «что n кратно 4», а также в «выровненном доступе», что, очевидно, является ограниченным вариантом использования для специалистов.

Реалистично, для циклов общего назначения, таких как в переносимых библиотеках, где n не может быть ограничено таким образом, накладные расходы на настройку и очистку для SIMD, чтобы справиться с некратными ширине SIMD, могут значительно превысить количество инструкций внутри самого цикла. Предполагая худший случай, что оборудование не может выполнять невыровненные доступы к памяти SIMD, реальный алгоритм будет:

Восьмиуровневый SIMD требует повторения алгоритма внутреннего цикла сначала с четырехуровневыми элементами SIMD, затем с двухуровневыми элементами SIMD, затем с одним (скалярным) элементом, с проверкой и ветвлением между каждым элементом, чтобы охватить первый и последний оставшиеся элементы SIMD (0 <= n <= 7).

Это более чем утроит размер кода, фактически в крайних случаях это приводит к увеличению количества инструкций на порядок ! Это можно легко продемонстрировать , скомпилировав пример iaxpy для AVX-512 , используя параметры "-O3 -march=knl"gcc .

Со временем, поскольку ISA развивается и продолжает увеличивать производительность, это приводит к тому, что архитекторы ISA добавляют 2-широкий SIMD, затем 4-широкий SIMD, затем 8-широкий и выше. Таким образом, можно понять, почему AVX-512 существует в x86.

Без прогнозирования, чем шире ширина SIMD, тем хуже становятся проблемы, что приводит к массовому увеличению числа кодов операций, снижению производительности, дополнительному потреблению энергии и ненужной сложности программного обеспечения. [19]

Векторные процессоры, с другой стороны, предназначены для выдачи вычислений переменной длины для произвольного количества n, и, таким образом, требуют очень мало настройки и никакой очистки. Даже по сравнению с теми SIMD ISA, которые имеют маски (но не имеют setvlинструкций), векторные процессоры производят гораздо более компактный код, поскольку им не нужно выполнять явное вычисление маски для покрытия последних нескольких элементов (показано ниже).

Предсказанный SIMD

Если предположить гипотетическую предикативную (с возможностью работы с маской) SIMD ISA и снова предположить, что инструкции SIMD могут справляться с невыровненными данными, то цикл инструкций будет выглядеть следующим образом:

vloop: # подготовить маску. Несколько ISA имеют min хотя min t0 , n , $4 ; t0 = min(n, 4) shift m , $1 , t0 ; m = 1<<t0 sub m , m , $1 ; m = (1<<t0)-1 # теперь выполнить операцию, замаскированную m битами load32x4 v1 , x , m load32x4 v2 , y , m mul32x4 v1 , a , v1 , m ; v1 := v1 * a add32x4 v3 , v1 , v2 , m ; v3 := v1 + v2 store32x4 v3 , y , m # обновить x, y и n для следующего цикла addl x , t0 * 4 ; x := x + t0*4 addl y , t0 * 4 subl n , n , t0 ; n := n - t0 # цикл? jgz n , vloop ; вернуться назад, если n > 0 out: ret                                                            

Здесь можно увидеть, что код намного чище, но немного сложнее: по крайней мере, однако, нет никакой настройки или очистки: на последней итерации цикла маска предиката будет установлена ​​либо на 0b0000, 0b0001, 0b0011, 0b0111 или 0b1111, что приведет к выполнению от 0 до 4 операций с элементами SIMD соответственно. Еще одно потенциальное осложнение: некоторые RISC ISA не имеют инструкции "min", вместо этого необходимо использовать ветвь или скалярное предикатное сравнение.

Ясно, что предицированный SIMD по крайней мере заслуживает термина "способный к вектору", поскольку он может справляться с векторами переменной длины, используя маски предикатов. Однако последний шаг к "истинному" векторному ISA заключается в том, чтобы вообще не иметь никаких свидетельств в ISA о ширине SIMD, оставляя это полностью на усмотрение оборудования.

Чистый (истинный) вектор ISA

Для векторных ISA в стиле Cray, таких как RVV, используется инструкция, называемая «setvl» (установить длину вектора). Аппаратное обеспечение сначала определяет, сколько значений данных оно может обработать в одном «векторе»: это могут быть либо фактические регистры, либо внутренний цикл (гибридный подход, упомянутый выше). Это максимальное количество (количество аппаратных «полос») называется «MVL» (максимальная длина вектора). Обратите внимание, что, как видно в SX-Aurora и Videocore IV, MVL может быть фактическим количеством аппаратных полос или виртуальным . (Примечание: как упоминалось в руководстве по ARM SVE2, программисты не должны совершать ошибку, предполагая фиксированную ширину вектора: следовательно, MVL — это не та величина, которую программисту нужно знать. Это может немного сбивать с толку после многих лет мышления SIMD). [ тон ]

При вызове setvl с числом ожидающих обработки элементов данных, "setvl" разрешено (по сути, требуется) ограничить его максимальной длиной вектора (MVL) и, таким образом, возвращает фактическое число, которое может быть обработано оборудованием в последующих векторных инструкциях, и устанавливает внутренний специальный регистр "VL" на то же самое значение. ARM называет эту технику "независимым от длины вектора" программированием в своих руководствах по SVE2. [20]

Ниже представлен ассемблер вектора в стиле Cray для того же цикла в стиле SIMD, что и выше. Обратите внимание, что t0 (который, содержащий удобную копию VL, может меняться) используется вместо жестко закодированных констант:

vloop: setvl t0 , n # VL=t0=min(MVL, n) vld32 v0 , x # загрузить вектор x vld32 v1 , y # загрузить вектор y vmadd32 v1 , v0 , a # v1 += v0 * a vst32 v1 , y # сохранить Y add y , t0 * 4 # увеличить y на VL*4 add x , t0 * 4 # увеличить x на VL*4 sub n , t0 # n -= VL (t0) bnez n , vloop # повторить, если n != 0                                     

По сути, это не сильно отличается от версии SIMD (обрабатывает 4 элемента данных за цикл) или от первоначальной скалярной версии (обрабатывает только один). n по-прежнему содержит количество элементов данных, оставшихся для обработки, но t0 содержит копию VL — количество, которое будет обработано в каждой итерации. t0 вычитается из n после каждой итерации, и если n равно нулю, то все элементы были обработаны.

При сравнении с вариантом сборки Predicated SIMD следует отметить ряд моментов:

  1. В инструкцию setvlвстроена minинструкция
  2. Если вариант SIMD жестко закодировал как ширину (4) в создании маски , так и в ширине SIMD (load32x4 и т. д.), то векторные эквиваленты ISA не имеют такого ограничения. Это делает векторные программы как переносимыми, так и независимыми от поставщика и перспективными.
  3. Настройка VL фактически создает скрытую предикатную маску , которая автоматически применяется к векторам.
  4. Если в случае предицированного SIMD длина бита маски ограничена той, которая может храниться в скалярном (или специальном масочном) регистре, то регистры масок векторного ISA не имеют такого ограничения. Векторы Cray-I могли быть чуть более 1000 элементов (в 1977 году).

Таким образом, можно очень наглядно увидеть, как векторные ISA сокращают количество инструкций.

Также обратите внимание, что, как и в предицированном варианте SIMD, указатели на x и y продвигаются на t0, умноженное на четыре, поскольку они оба указывают на 32-битные данные, но n уменьшается на t0. По сравнению с ассемблером SIMD фиксированного размера, видимых различий очень мало: x и y продвигаются на жестко закодированную константу 16, n уменьшается на жестко закодированную 4, поэтому изначально трудно оценить значимость. Разница заключается в осознании того, что векторное оборудование может быть способно выполнять 4 одновременные операции, или 64, или 10 000, это будет точно такой же векторный ассемблер для всех из них, и все равно не будет кода очистки SIMD . Даже по сравнению с SIMD с предикатами он все еще более компактен, понятен, элегантен и использует меньше ресурсов.

Это не только гораздо более компактная программа (экономит размер кэша L1), но, как уже упоминалось ранее, векторная версия может передавать гораздо больше данных на обработку в АЛУ, что опять же экономит электроэнергию, поскольку декодирование и выдача инструкций могут простаивать.

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

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

Этот пример начинается с алгоритма, включающего сокращение. Как и в предыдущем примере, сначала он будет показан в скалярных инструкциях, затем SIMD и, наконец, в векторных инструкциях, начиная с c :

void ( size_t n , int a , const int x []) { int y = 0 ; для ( size_t i = 0 ; i < n ; i ++ ) y += x [ i ]; return y ; }                          

Здесь аккумулятор (y) используется для суммирования всех значений в массиве x.

Скалярный ассемблер

Скалярная версия этого кода загружает каждый из x, добавляет его к y и выполняет цикл:

 set y , 0 ; y инициализирован нулем loop: load32 r1 , x ; загрузить одни 32-битные данные add32 y , y , r1 ; y := y + r1 addl x , x , $4 ; x := x + 4 subl n , n , $1 ; n := n - 1 jgz n , loop ; вернуться назад, если n > 0 out: ret y ; возвращает результат, y                             

Это очень просто. «y» начинается с нуля, 32-битные целые числа загружаются по одному в r1, добавляются к y, а адрес массива «x» перемещается к следующему элементу массива.

Сокращение SIMD

Вот тут-то и начинаются проблемы. SIMD по своей конструкции не способен выполнять арифметические операции «между элементами». Элемент 0 одного регистра SIMD может быть добавлен к Элементу 0 другого регистра, но Элемент 0 не может быть добавлен ни к чему, кроме другого Элемента 0. Это накладывает некоторые серьезные ограничения на потенциальные реализации. Для простоты можно предположить, что n равно ровно 8:

 addl r3 , x , $16 ; для вторых 4 из x load32x4 v1 , x ; первых 4 из x load32x4 v2 , r3 ; вторых 4 из x add32x4 v1 , v2 , v1 ; добавить 2 группы                 

На данный момент было выполнено четыре добавления:

но с 4-широким SIMD, неспособным по замыслу складывать x[0]+x[1], например, дела быстро идут под откос, как это было с общим случаем использования SIMD для циклов IAXPY общего назначения. Чтобы суммировать четыре частичных результата, можно использовать двухширокий SIMD, за которым следует одно скалярное сложение, чтобы в конечном итоге получить ответ, но часто данные должны быть переданы из выделенных регистров SIMD до того, как будет выполнено последнее скалярное вычисление.

Даже с общим циклом (n не фиксировано) единственный способ использовать 4-wide SIMD — это предположить четыре отдельных «потока», каждый из которых смещен на четыре элемента. Наконец, четыре частичных результата должны быть просуммированы. Другие методы включают перемешивание: примеры в сети можно найти для AVX-512 того, как сделать «горизонтальную сумму» [21] [22]

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

Вектор ISA редукция

Наборы векторных инструкций имеют встроенные в ISA арифметические операции редукции . Если предполагается, что n меньше или равно максимальной длине вектора, требуются только три инструкции:

 setvl t0 , n # VL=t0=min(MVL, n) vld32 v0 , x # загрузить вектор x vredadd32 y , v0 # уменьшить-добавить к y           

Код, когда n больше максимальной длины вектора, не намного сложнее и представляет собой шаблон, аналогичный первому примеру («IAXPY»).

 set y , 0 vloop: setvl t0 , n # VL=t0=min(MVL, n) vld32 v0 , x # загрузить вектор x vredadd32 y , y , v0 # добавить все x в y add x , t0 * 4 # увеличить x на VL*4 sub n , t0 # n -= VL (t0) bnez n , vloop # повторить, если n != 0 ret y                             

Простота алгоритма очевидна по сравнению с SIMD. Опять же, как и в примере IAXPY, алгоритм не зависит от длины (даже во встроенных реализациях, где максимальная длина вектора может быть только один).

Реализации в аппаратном обеспечении могут, если они уверены, что будет получен правильный ответ, выполнять сокращение параллельно. Некоторые векторные ISA предлагают параллельный режим сокращения как явную опцию, когда программист знает, что любые потенциальные ошибки округления не имеют значения, а низкая задержка имеет решающее значение. [23]

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

Выводы из примеров

По сравнению с любым SIMD-процессором, претендующим на звание векторного процессора, порядок уменьшения размера программы почти шокирует. Однако этот уровень элегантности на уровне ISA имеет довольно высокую цену на аппаратном уровне:

  1. Из примера IAXPY видно, что в отличие от процессоров SIMD, которые могут упростить свое внутреннее оборудование, избегая работы с невыровненным доступом к памяти, векторный процессор не может обойтись без такого упрощения: написанные алгоритмы изначально полагаются на успешность векторной загрузки и сохранения, независимо от выравнивания начала вектора.
  2. В то время как из примера с сокращением можно увидеть, что, помимо инструкций перестановки , SIMD по определению полностью избегает межполосных операций (элемент 0 может быть добавлен только к другому элементу 0), векторные процессоры решают эту проблему в лоб. То, что программисты вынуждены делать в программном обеспечении (используя перемешивание и другие трюки, чтобы переместить данные в нужную «полосу»), векторные процессоры должны делать в аппаратном обеспечении, автоматически.

В целом, тогда есть выбор: либо иметь

  1. сложное программное обеспечение и упрощенное аппаратное обеспечение (SIMD)
  2. упрощенное программное обеспечение и сложное аппаратное обеспечение (векторные процессоры)

Именно эти существенные различия отличают векторный процессор от процессора с SIMD.

Возможности векторного процессора

Хотя многие SIMD ISA заимствуют или вдохновлены списком ниже, типичными характеристиками векторного процессора являются: [24] [25] [26]

Возможности векторной обработки на GPU

Поскольку многим приложениям 3D- шейдеров требуются тригонометрические операции, а также короткие векторы для общих операций (RGB, ARGB, XYZ, XYZW), в современных графических процессорах обычно присутствует поддержка следующих функций, в дополнение к тем, которые имеются в векторных процессорах:

Первая ошибка (или неудача)

Представленная в ARM SVE2 и RISC-V RVV представляет собой концепцию спекулятивных последовательных векторных нагрузок. ARM SVE2 имеет специальный регистр, называемый «First Fault Register», [35] , где RVV изменяет (обрезает) длину вектора (VL). [36]

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

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

Это начинает намекать на причину, по которой ffirst является настолько инновационным, и лучше всего иллюстрируется memcpy или strcpy, когда они реализованы с помощью стандартного 128-битного непредикатного не-ffirst SIMD. Для IBM POWER9 количество оптимизированных вручную инструкций для реализации strncpy превышает 240. [37] Напротив, та же процедура strncpy в оптимизированном вручную ассемблере RVV состоит всего из 22 инструкций. [38]

Приведенный выше пример SIMD может потенциально дать сбой и выйти из строя в конце памяти из-за попыток прочитать слишком много значений: он также может вызвать значительное количество ошибок страниц или невыровненных ошибок, аналогично пересекая границы. Напротив, предоставляя векторной архитектуре свободу решать, сколько элементов загружать, первая часть strncpy, если изначально начинается на неоптимальной границе памяти, может возвращать ровно столько загрузок, чтобы на последующих итерациях цикла пакеты векторизованных чтений памяти были оптимально выровнены с базовыми кэшами и виртуальными схемами памяти. Кроме того, оборудование может выбрать использование возможности завершить чтение памяти любой заданной итерации цикла точно на границе страницы (избегая дорогостоящего второго поиска TLB), со спекулятивным выполнением, подготавливающим следующую страницу виртуальной памяти, пока данные все еще обрабатываются в текущем цикле. Все это определяется оборудованием, а не самой программой. [39]

Производительность и ускорение

Пусть r будет векторным коэффициентом скорости, а f — коэффициентом векторизации. Если время, необходимое векторному блоку для сложения массива из 64 чисел, в 10 раз быстрее, чем его эквивалентному скалярному аналогу, r = 10. Кроме того, если общее количество операций в программе равно 100, из которых только 10 являются скалярными (после векторизации), то f = 0,9, т. е. 90% работы выполняется векторным блоком. Это следует из достижимого ускорения:

Таким образом, даже если производительность векторного блока очень высока ( ), есть ускорение меньше , что говорит о том, что отношение f имеет решающее значение для производительности. Это отношение зависит от эффективности компиляции, например, от смежности элементов в памяти.

Смотрите также

Ссылки

  1. Паркинсон, Деннис (17 июня 1976 г.). «Компьютеры тысячами». New Scientist . С. 626–627 . Получено 7 июля 2024 г.
  2. ^ Б. Н. Малиновский (1995). История вычислительной техники в их лицах . КИТ. ISBN 5770761318.
  3. ^ MIAOW Вертикальная исследовательская группа
  4. ^ MIAOW GPU
  5. ^ "Andes анонсирует многоядерный 1024-битный векторный процессор RISC-V: AX45MPV" (пресс-релиз). GlobeNewswire. 7 декабря 2022 г. Получено 23 декабря 2022 г.
  6. ^ Мияока, Y.; Чой, J.; Тогава, N.; Янагисава, M.; Оцуки, T. (2002). Алгоритм генерации аппаратных блоков для синтеза процессорных ядер с упакованными инструкциями типа SIMD . Азиатско-Тихоокеанская конференция по схемам и системам. Том 1. С. 171–176. doi :10.1109/APCCAS.2002.1114930. hdl : 2065/10689 .
  7. ^ "Riscv-v-spec/V-spec.adoc в мастере · riscv/Riscv-v-spec" . Гитхаб . 16 июня 2023 г.
  8. ^ «Справочное руководство по языку ассемблера Vector Engine» (PDF) . 16 июня 2023 г.
  9. ^ «Документация – Разработчик Arm».
  10. ^ "Векторная архитектура". 27 апреля 2020 г.
  11. ^ Векторные и SIMD-процессоры, слайды 12-13
  12. ^ Обработка массивов и векторов, слайды 5-7
  13. ^ SIMD против Vector GPU, слайды 22-24
  14. ^ Паттерсон, Дэвид А .; Хеннесси, Джон Л. (1998). Организация и проектирование компьютеров: интерфейс оборудования и программного обеспечения, стр. 751-2 (2-е изд.). Морган Кауфманн. стр. 751-2. ISBN 155860491X.
  15. ^ "Riscv-v-spec/V-spec.adoc в мастере · riscv/Riscv-v-spec" . Гитхаб . 19 ноября 2022 г.
  16. ^ Руководство программиста Videocore IV
  17. ^ Анализ Videocore IV QPU Джеффа Буша
  18. ^ "Кодирование для Neon - Часть 3. Умножение матриц". 11 сентября 2013 г.
  19. ^ SIMD считается вредным
  20. ^ Учебник ARM SVE2
  21. ^ "Sse - трансляция 1-к-4 и уменьшение 4-к-1 в AVX-512".
  22. ^ «Ассемблер — самый быстрый способ выполнить горизонтальную векторную сумму SSE (или другое сокращение)».
  23. ^ "Riscv-v-spec/V-spec.adoc в мастере · riscv/Riscv-v-spec" . Гитхаб . 19 ноября 2022 г.
  24. ^ Обзор Cray
  25. ^ RISC-V RVV ISA
  26. ^ Обзор SX-Arora
  27. ^ Инструкции по сбору-разбросу регистра RVV
  28. ^ "Процессор IBM POWER10 - Уильям Старк и Брайан В. Томпто, IBM". YouTube . Архивировано из оригинала 2021-12-11.
  29. ^ Морейра, Хосе Э.; Бартон, Кит; Баттл, Стивен; Бергнер, Питер; Бертран, Рамон; Бхат, Пунит; Кальдейра, Педро; Эдельсон, Дэвид; Фоссум, Гордон; Фрей, Брэд; Иванович, Неманья; Кершнер, Чип; Лим, Винсент; Капур, Шакти; Тулио Мачадо Фильо; Сильвия Мелитта Мюллер; Олссон, Бретт; Садашивам, Сатиш; Салейл, Батист; Шмидт, Билл; Шринивасарагаван, Раджалакшми; Шриватсан, Шричаран; Томпто, Брайан; Вагнер, Андреас; Ву, Нельсон (2021). «Матричное математическое средство для процессоров Power ISA (TM)». arXiv : 2104.03142 [cs.AR].
  30. ^ Крикелис, Анаргирос (1996). «Модульный массивно-параллельный процессор для обработки объемной визуализации». Высокопроизводительные вычисления для компьютерной графики и визуализации . стр. 101–124. doi :10.1007/978-1-4471-1011-8_8. ISBN 978-3-540-76016-0.
  31. ^ «Руководство по программированию CUDA C++».
  32. ^ LMUL > 1 в RVV
  33. ^ Отмененный патент США US20110227920-0096
  34. ^ Видеоядро IV QPU
  35. ^ Введение в ARM SVE2
  36. ^ Нагрузки RVV, возникающие при первом сбое
  37. ^ Исправление к libc6 для добавления оптимизированного POWER9 strncpy
  38. ^ Пример RVV strncpy
  39. ^ Статья ARM SVE2 Н. Стивенса