stringtranslate.com

Procesador vectorial

En informática , un procesador vectorial o procesador de matrices es una unidad central de procesamiento (CPU) que implementa un conjunto de instrucciones donde sus instrucciones están diseñadas para operar de manera eficiente y efectiva sobre grandes matrices unidimensionales de datos llamadas vectores . Esto contrasta con los procesadores escalares , cuyas instrucciones operan solo con elementos de datos únicos, y en contraste con algunos de esos mismos procesadores escalares que tienen instrucciones únicas adicionales, datos múltiples (SIMD) o unidades aritméticas SWAR . Los procesadores vectoriales pueden mejorar enormemente el rendimiento en determinadas cargas de trabajo, en particular la simulación numérica y tareas similares. Las técnicas de procesamiento vectorial también operan en el hardware de consolas de videojuegos y en aceleradores de gráficos .

Las máquinas vectoriales aparecieron a principios de la década de 1970 y dominaron el diseño de supercomputadoras desde la década de 1970 hasta la de 1990, en particular las diversas plataformas Cray . La rápida caída en la relación precio-rendimiento de los diseños de microprocesadores convencionales condujo a una disminución de las supercomputadoras vectoriales durante la década de 1990.

Historia

Investigación y desarrollo tempranos

El desarrollo del procesamiento vectorial comenzó a principios de la década de 1960 en Westinghouse Electric Corporation en su proyecto Solomon . El objetivo de Solomon era aumentar drásticamente el rendimiento matemático mediante el uso de una gran cantidad de coprocesadores simples bajo el control de una única unidad central de procesamiento (CPU) maestra. La CPU alimentaba una única instrucción común a todas las unidades lógicas aritméticas (ALU), una por ciclo, pero con un punto de datos diferente para que cada una trabajara. Esto permitió a la máquina Solomon aplicar un único algoritmo a un gran conjunto de datos , alimentado en forma de matriz.

En 1962, Westinghouse canceló el proyecto, pero la Universidad de Illinois en Urbana-Champaign lo reinició como ILLIAC IV . Su versión del diseño originalmente requería una máquina de 1 GFLOPS con 256 ALU, pero, cuando finalmente se entregó en 1972, solo tenía 64 ALU y solo podía alcanzar de 100 a 150 MFLOPS. Sin embargo, demostró que el concepto básico era sólido y, cuando se utilizaba en aplicaciones con uso intensivo de datos, como la dinámica de fluidos computacional , el ILLIAC era la máquina más rápida del mundo. El enfoque ILLIAC de utilizar ALU separadas para cada elemento de datos no es común en diseños posteriores y, a menudo, se lo denomina en una categoría separada: computación masivamente paralela . Por esta época, Flynn categorizó este tipo de procesamiento como una forma temprana de instrucción única, subprocesos múltiples (SIMT).

Computadora para operaciones con funciones.

Kartsev presentó y desarrolló una computadora para operaciones con funciones en 1967. [1]

Supercomputadoras

Las primeras supercomputadoras vectoriales son Control Data Corporation STAR-100 y Texas Instruments Advanced Scientific Computer (ASC), que se introdujeron en 1974 y 1972, respectivamente.

La ALU básica ASC (es decir, "una tubería") utilizaba una arquitectura de canalización que admitía cálculos escalares y vectoriales, con un rendimiento máximo que alcanzaba aproximadamente 20 MFLOPS, lo que se logra fácilmente al procesar vectores largos. Las configuraciones de ALU ampliadas admitían "dos tubos" o "cuatro tubos" con una ganancia de rendimiento correspondiente de 2X o 4X. El ancho de banda de la memoria era suficiente para admitir estos modos ampliados.

Por lo demás, el STAR-100 era más lento que las propias supercomputadoras de CDC, como el CDC 7600 , pero en tareas relacionadas con datos podían mantenerse al día y al mismo tiempo ser mucho más pequeños y menos costosos. Sin embargo, la máquina también tomó un tiempo considerable para decodificar las instrucciones vectoriales y prepararse para ejecutar el proceso, por lo que requirió conjuntos de datos muy específicos para trabajar antes de acelerar algo.

La técnica vectorial fue plenamente explotada por primera vez en 1976 por el famoso Cray-1 . En lugar de dejar los datos en la memoria como el STAR-100 y el ASC, el diseño de Cray tenía ocho registros vectoriales , que contenían sesenta y cuatro palabras de 64 bits cada uno. Las instrucciones vectoriales se aplicaron entre registros, lo que es mucho más rápido que hablar con la memoria principal. Mientras que el STAR-100 aplicaría una sola operación a través de un vector largo en la memoria y luego pasaría a la siguiente operación, el diseño de Cray cargaría una sección más pequeña del vector en registros y luego aplicaría tantas operaciones como fuera posible a esos datos. , evitando así muchas de las operaciones de acceso a la memoria mucho más lentas.

El diseño de Cray utilizó el paralelismo de tuberías para implementar instrucciones vectoriales en lugar de múltiples ALU. Además, el diseño tenía canales completamente separados para diferentes instrucciones; por ejemplo, la suma/resta se implementó en un hardware diferente al de la multiplicación. Esto permitió canalizar un lote de instrucciones vectoriales en cada una de las subunidades de ALU, una técnica que llamaron encadenamiento de vectores . El Cray-1 normalmente tenía un rendimiento de alrededor de 80 MFLOPS, pero con hasta tres cadenas en funcionamiento podía alcanzar un máximo de 240 MFLOPS y un promedio de alrededor de 150, mucho más rápido que cualquier máquina de la época.

Módulo procesador Cray J90 con cuatro procesadores escalares/vectoriales

Siguieron otros ejemplos. Control Data Corporation intentó volver a entrar en el mercado de alta gama con su máquina ETA-10 , pero se vendió mal y lo aprovecharon como una oportunidad para abandonar por completo el campo de la supercomputación. A principios y mediados de la década de 1980, las empresas japonesas ( Fujitsu , Hitachi y Nippon Electric Corporation (NEC) introdujeron máquinas vectoriales basadas en registros similares a la Cray-1, que normalmente eran un poco más rápidas y mucho más pequeñas. Sistemas de punto flotante (FPS) con sede en Oregón . construyó procesadores de matriz complementarios para minicomputadoras y luego construyó sus propias minisupercomputadoras .

En todo momento, Cray continuó siendo líder en rendimiento, superando continuamente a la competencia con una serie de máquinas que llevaron a Cray-2 , Cray X-MP y Cray Y-MP . Desde entonces, el mercado de supercomputadoras se ha centrado mucho más en el procesamiento paralelo masivo que en mejores implementaciones de procesadores vectoriales. Sin embargo, reconociendo los beneficios del procesamiento vectorial, IBM desarrolló una arquitectura vectorial virtual para usar en supercomputadoras que acoplan varios procesadores escalares para que actúen como un procesador vectorial.

Aunque las supercomputadoras vectoriales parecidas a la Cray-1 son menos populares hoy en día, NEC ha seguido fabricando este tipo de computadoras hasta el día de hoy con su serie de computadoras SX . Más recientemente, el SX-Aurora TSUBASA coloca el procesador y 24 o 48 gigabytes de memoria en un módulo HBM 2 dentro de una tarjeta que físicamente se parece a un coprocesador de gráficos, pero en lugar de servir como coprocesador, es la computadora principal con la computadora compatible con PC a la que está conectado cumple funciones de soporte.

GPU

Las unidades de procesamiento de gráficos ( GPU ) modernas incluyen una serie de canales de sombreado que pueden ser controlados por núcleos de cómputo y pueden considerarse procesadores vectoriales (utilizando una estrategia similar para ocultar las latencias de memoria). Como se muestra en el artículo de Flynn de 1972, el factor distintivo clave de las GPU basadas en SIMT es que tienen un único decodificador-transmisor de instrucciones, pero que los núcleos que reciben y ejecutan esa misma instrucción son, por lo demás, razonablemente normales: sus propias ALU, sus propios archivos de registro, sus propias unidades de carga/almacenamiento y sus propios cachés de datos L1 independientes. Por lo tanto, aunque todos los núcleos ejecutan simultáneamente exactamente la misma instrucción al unísono entre sí, lo hacen con datos completamente diferentes de ubicaciones de memoria completamente diferentes. Esto es significativamente más complejo y complicado que "Packed SIMD" , que se limita estrictamente a la ejecución de operaciones aritméticas canalizadas en paralelo únicamente. Aunque los detalles internos exactos de las GPU comerciales actuales son secretos de propiedad exclusiva, el equipo de MIAOW [2] pudo reunir información anecdótica suficiente para implementar un subconjunto de la arquitectura AMDGPU. [3]

Desarrollo reciente

Se están diseñando varias arquitecturas de CPU modernas como procesadores vectoriales. La extensión vectorial RISC-V sigue principios similares a los de los primeros procesadores vectoriales y se está implementando en productos comerciales como Andes Technology AX45MPV. [4] También se están desarrollando varias arquitecturas de procesadores vectoriales de código abierto , incluidas ForwardCom y Libre-SOC .

Comparación con arquitecturas modernas.

A partir de 2016, la mayoría de las CPU básicas implementan arquitecturas que cuentan con instrucciones SIMD de longitud fija. A primera vista, estos pueden considerarse una forma de procesamiento vectorial porque operan en múltiples conjuntos de datos (vectorizados, de longitud explícita) y toman prestadas características de los procesadores vectoriales. Sin embargo, por definición, la adición de SIMD no puede, por sí sola, calificar a un procesador como un procesador vectorial real , porque SIMD es de longitud fija y los vectores son de longitud variable . La diferencia se ilustra a continuación con ejemplos que muestran y comparan las tres categorías: SIMD puro, SIMD predicado y procesamiento vectorial puro. [ cita necesaria ]

Otros diseños de CPU incluyen algunas instrucciones múltiples para el procesamiento de vectores en múltiples conjuntos de datos (vectorizados), generalmente conocidos como MIMD (Instrucción múltiple, Datos múltiples) y realizados con VLIW (Palabra de instrucción muy larga) y EPIC (Computación de instrucción explícitamente paralela). El procesador vectorial Fujitsu FR-V VLIW/vector combina ambas tecnologías.

Diferencia entre procesadores SIMD y vectoriales

Los conjuntos de instrucciones SIMD carecen de características cruciales en comparación con los conjuntos de instrucciones vectoriales. El más importante de ellos es que los procesadores vectoriales, inherentemente por definición y diseño, siempre han sido de longitud variable desde sus inicios.

Mientras que a menudo se afirma erróneamente que el SIMD puro (ancho fijo, sin predicación) es "vectorial" (porque SIMD procesa datos que resultan ser vectores), a través de un análisis minucioso y la comparación de ISA históricos y modernos, se puede observar que los ISA vectoriales reales tiene las siguientes características que ningún SIMD ISA tiene: [ cita necesaria ]

SIMD predicado (parte de la taxonomía de Flynn ), que son máscaras de predicados individuales integrales a nivel de elemento en cada instrucción vectorial, como ahora está disponible en ARM SVE2. [8] Y AVX-512 , casi califica como un procesador vectorial. [ ¿ cómo? ] SIMD predicado utiliza ALU SIMD de ancho fijo pero permite la activación de unidades controlada localmente (predicada) para proporcionar la apariencia de vectores de longitud variable. Los ejemplos siguientes ayudan a explicar estas distinciones categóricas.

SIMD, debido a que utiliza procesamiento por lotes de ancho fijo, no puede, por diseño, hacer frente a la iteración y la reducción. Esto se ilustra con más detalle con ejemplos a continuación.

Además, los procesadores vectoriales pueden ser más eficientes en cuanto a recursos al utilizar hardware más lento y ahorrar energía, pero aun así lograr rendimiento y tener menos latencia que SIMD, a través del encadenamiento de vectores . [9] [10]

Considere tanto un procesador SIMD como un procesador vectorial trabajando en 4 elementos de 64 bits, realizando una secuencia CARGAR, AGREGAR, MULTIPLICAR y ALMACENAR. Si el ancho de SIMD es 4, entonces el procesador SIMD debe CARGAR cuatro elementos por completo antes de poder pasar a los ADD, debe completar todos los ADD antes de poder pasar a MULTIPLY y, de la misma manera, debe completar todos los MULTIPLY antes de poder iniciar las TIENDAS. Esto es por definición y por diseño. [11]

Tener que realizar 4 cargas simultáneas de 64 bits y TIENDAS de 64 bits es muy costoso en hardware (rutas de datos de 256 bits a la memoria). Tener 4x ALU de 64 bits, especialmente MULTIPLY, también. Para evitar estos altos costos, un procesador SIMD tendría que tener 1 LOAD de 64 bits de ancho, 1 STORE de 64 bits de ancho y solo 2 ALU de 64 bits de ancho. Como se muestra en el diagrama, que supone un modelo de ejecución de múltiples cuestiones , las consecuencias son que las operaciones ahora tardan más en completarse. Si no es posible emitir varias emisiones, las operaciones tardarán aún más porque es posible que la LD no se emita (inicie) al mismo tiempo que las primeras ADD, y así sucesivamente. Si solo hay 4 ALU SIMD de 64 bits de ancho, el tiempo de finalización es aún peor: solo cuando se hayan completado las cuatro CARGAS pueden comenzar las operaciones SIMD, y solo cuando se hayan completado todas las operaciones de ALU pueden comenzar las TIENDAS.

Un procesador vectorial, por el contrario, incluso si es de un solo problema y no utiliza SIMD ALU, solo tiene 1 LOAD de 64 bits de ancho, 1 STORE de 64 bits de ancho (y, como en el Cray-1 , la capacidad de ejecutar MULTIPLY simultáneamente con ADD), puede completar las cuatro operaciones más rápido que un procesador SIMD con 1 LOAD de ancho, 1 STORE de ancho y 2 SIMD de ancho. Esta utilización más eficiente de los recursos, debido al encadenamiento de vectores , es una ventaja y diferencia clave en comparación con SIMD. SIMD, por diseño y definición, no puede realizar encadenamiento excepto para todo el grupo de resultados. [12]

Descripción

En términos generales, las CPU pueden manipular uno o dos datos a la vez. Por ejemplo, la mayoría de las CPU tienen una instrucción que esencialmente dice "agregue A a B y coloque el resultado en C". Los datos para A, B y C podrían codificarse, al menos en teoría, directamente en la instrucción. Sin embargo, en una implementación eficiente las cosas rara vez son tan simples. Los datos rara vez se envían en formato sin procesar, sino que se "apuntan" pasando una dirección a una ubicación de memoria que contiene los datos. Decodificar esta dirección y sacar los datos de la memoria lleva algún tiempo, durante el cual la CPU tradicionalmente permanece inactiva esperando que aparezcan los datos solicitados. A medida que aumentaron las velocidades de la CPU, esta latencia de la memoria se ha convertido históricamente en un gran impedimento para el rendimiento; ver Muro de la memoria .

Para reducir la cantidad de tiempo consumido por estos pasos, la mayoría de las CPU modernas utilizan una técnica conocida como canalización de instrucciones en la que las instrucciones pasan por varias subunidades a su vez. La primera subunidad lee la dirección y la decodifica, la siguiente "busca" los valores en esas direcciones y la siguiente hace los cálculos por sí misma. Con la canalización, el "truco" consiste en comenzar a decodificar la siguiente instrucción incluso antes de que la primera haya salido de la CPU, al estilo de una línea de ensamblaje , de modo que el decodificador de direcciones esté en uso constantemente. Cualquier instrucción en particular tarda la misma cantidad de tiempo en completarse, un tiempo conocido como latencia , pero la CPU puede procesar un lote completo de operaciones, de forma superpuesta, mucho más rápido y más eficientemente que si lo hiciera una a la vez.

Los procesadores vectoriales llevan este concepto un paso más allá. En lugar de canalizar sólo las instrucciones, también canalizan los datos en sí. El procesador recibe instrucciones que dicen no sólo que sume A a B, sino que sume todos los números "de aquí a aquí" a todos los números "de allí hasta allá". En lugar de tener que decodificar instrucciones constantemente y luego buscar los datos necesarios para completarlas, el procesador lee una sola instrucción de la memoria, y en la definición de la instrucción misma simplemente se da a entender que la instrucción operará nuevamente con otro elemento de datos. en una dirección un incremento mayor que el anterior. Esto permite un ahorro significativo en el tiempo de decodificación.

Para ilustrar la diferencia que esto puede hacer, considere la sencilla tarea de sumar dos grupos de 10 números. En un lenguaje de programación normal, se escribiría un "bucle" que tomaría cada uno de los pares de números por turno y luego los sumaría. Para la CPU, esto se vería así:

; Máquina RISC hipotética ; supongamos que a, b y c son ubicaciones de memoria en sus respectivos registros ; sumar 10 números en a a 10 números en b, almacenar los resultados en c mover $10 , contar ; contar: = 10 bucle: cargar r1 , a cargar r2 , b agregar r3 , r1 , r2 ; r3: = r1 + r2 almacenar r3 , c agregar a , a , $4 ; seguir adelante agregar b , b , $4 agregar c , c , $4 conteo dec ; decrementar el recuento de jnez , bucle ; bucle hacia atrás si el conteo aún no es 0 ret                                       

Pero para un procesador vectorial, esta tarea parece considerablemente diferente:

; supongamos que tenemos registros vectoriales v1-v3 ; con tamaño igual o mayor a 10 mueve $10 , cuenta ; recuento = 10 vload v1 , a , recuento vload v2 , b , recuento vadd v3 , v1 , v2 vstore v3 , c , recuento ret                     

Tenga en cuenta la completa falta de bucles en las instrucciones, porque es el hardware el que ha realizado 10 operaciones secuenciales: efectivamente, el recuento de bucles se realiza explícitamente por instrucción .

Los ISA vectoriales estilo Cray van un paso más allá y proporcionan un registro de "recuento" global, llamado longitud del vector (VL):

; supongamos nuevamente que tenemos registros vectoriales v1-v3 ; con tamaño mayor o igual a 10 setvli $10 # Establecer longitud del vector VL=10 vload v1 , a # 10 carga desde a vload v2 , b # 10 carga desde b vadd v3 , v1 , v2 # 10 agrega vstore v3 , c # 10 se almacena en c ret                     

Hay varios ahorros inherentes a este enfoque. [13]

  1. sólo se necesitan tres traducciones de direcciones. Dependiendo de la arquitectura, esto puede representar un ahorro significativo por sí solo.
  2. Otro ahorro es buscar y decodificar la instrucción en sí, lo que debe hacerse sólo una vez en lugar de diez.
  3. El código en sí también es más pequeño, lo que puede conducir a un uso más eficiente de la memoria, una reducción del tamaño de la caché de instrucciones L1 y una reducción del consumo de energía.
  4. Con la reducción del tamaño del programa, la predicción de ramas es un trabajo más fácil.
  5. Dado que la longitud (equivalente al ancho SIMD) no está codificada en la instrucción, la codificación no solo es más compacta, sino que también está "preparada para el futuro" y permite que incluso los diseños de procesadores integrados consideren el uso de vectores únicamente para obtener todas las demás ventajas. , en lugar de optar por un alto rendimiento.

Además, en los ISA de procesador vectorial más modernos, se ha introducido "Fail on First" o "Fault First" (ver más abajo), lo que aporta aún más ventajas.

Pero más que eso, un procesador vectorial de alto rendimiento puede tener múltiples unidades funcionales sumando esos números en paralelo. No es necesaria la verificación de dependencias entre esos números ya que una instrucción vectorial especifica múltiples operaciones independientes. Esto simplifica la lógica de control requerida y puede mejorar aún más el rendimiento al evitar paradas. De este modo, las operaciones matemáticas se completaron mucho más rápido en general, siendo el factor limitante el tiempo necesario para recuperar los datos de la memoria.

No todos los problemas pueden atacarse con este tipo de solución. Incluir este tipo de instrucciones necesariamente agrega complejidad al núcleo de la CPU. Esa complejidad normalmente hace que otras instrucciones se ejecuten más lentamente, es decir, cuando no se suman muchos números seguidos. Las instrucciones más complejas también aumentan la complejidad de los decodificadores, lo que podría ralentizar la decodificación de las instrucciones más comunes, como la suma normal. ( Esto se puede mitigar un poco manteniendo todos los principios ISA según RISC : RVV solo agrega alrededor de 190 instrucciones vectoriales incluso con las funciones avanzadas. [14] )

Los procesadores vectoriales se diseñaron tradicionalmente para funcionar mejor sólo cuando hay grandes cantidades de datos con los que trabajar. Por esta razón, este tipo de CPU se encontraban principalmente en supercomputadoras , ya que las supercomputadoras en sí se encontraban, en general, en lugares como centros de predicción meteorológica y laboratorios de física, donde se "procesan" enormes cantidades de datos. Sin embargo, como se muestra arriba y lo demuestra RISC-V RVV, la eficiencia de los ISA vectoriales aporta otros beneficios que son convincentes incluso para casos de uso integrados.

Instrucciones vectoriales

El ejemplo de pseudocódigo vectorial anterior parte de la gran suposición de que la computadora vectorial puede procesar más de diez números en un lote. Para una mayor cantidad de números en el registro vectorial, resulta inviable que la computadora tenga un registro tan grande. Como resultado, el procesador vectorial obtiene la capacidad de realizar bucles por sí mismo o expone algún tipo de registro de control vectorial (estado) al programador, generalmente conocido como longitud vectorial.

Las instrucciones que se repiten automáticamente se encuentran en las primeras computadoras vectoriales como la STAR-100, donde la acción anterior se describiría en una sola instrucción (algo así como vadd c, a, b, $10). También se encuentran en la arquitectura x86 como REPprefijo. Sin embargo, de esta manera sólo se pueden realizar cálculos muy simples de manera efectiva en el hardware sin un aumento de costos muy grande. Dado que todos los operandos deben estar en la memoria para la arquitectura STAR-100, la latencia provocada por el acceso también se volvió enorme.

Curiosamente, Broadcom incluyó espacio en todas las operaciones vectoriales del Videocore IV ISA para un REPcampo, pero a diferencia del STAR-100 que usa memoria para sus repeticiones, las repeticiones del Videocore IV están en todas las operaciones, incluidas las operaciones vectoriales aritméticas. La duración de la repetición puede ser un pequeño rango de potencia de dos o provenir de uno de los registros escalares. [15]

El Cray-1 introdujo la idea de utilizar registros de procesador para almacenar datos vectoriales en lotes. Las longitudes de los lotes (longitud del vector, VL) podrían establecerse dinámicamente con una instrucción especial; la importancia en comparación con Videocore IV (y, de manera crucial, como se mostrará a continuación, también con SIMD) es que la longitud de repetición no tiene que ser parte de la codificación de instrucciones. De esta manera, se puede realizar mucho más trabajo en cada lote; la codificación de instrucciones también es mucho más elegante y compacta. El único inconveniente es que para aprovechar al máximo esta capacidad adicional de procesamiento por lotes, la carga de memoria y la velocidad de almacenamiento también tuvieron que aumentar en consecuencia. A veces esto es afirmado [ ¿por quién? ] es una desventaja de los procesadores vectoriales estilo Cray: en realidad, es parte del logro de un alto rendimiento, como se ve en las GPU , que enfrentan exactamente el mismo problema.

Las computadoras SIMD modernas afirman mejorar las primeras Cray al usar directamente múltiples ALU, para un mayor grado de paralelismo en comparación con el uso exclusivo de la tubería escalar normal. Los procesadores vectoriales modernos (como el SX-Aurora TSUBASA ) combinan ambos, emitiendo múltiples datos a múltiples SIMD ALU internas canalizadas, siendo el número emitido elegido dinámicamente por el programa vectorial en tiempo de ejecución. Las máscaras se pueden usar para cargar y almacenar datos de forma selectiva en ubicaciones de memoria, y usar esas mismas máscaras para desactivar selectivamente el elemento de procesamiento de las ALU SIMD. Algunos procesadores con SIMD ( AVX-512 , ARM SVE2 ) son capaces de realizar este tipo de procesamiento selectivo por elemento ( "predicado" ), y son estos los que en cierto modo merecen la nomenclatura "procesador vectorial" o al menos merecen el reclamo de siendo capaz de "procesamiento de vectores". Los procesadores SIMD sin predicación por elemento ( MMX , SSE , AltiVec ) categóricamente no lo hacen.

Las GPU modernas, que tienen muchas unidades de cómputo pequeñas, cada una con sus propias ALU SIMD independientes, utilizan subprocesos múltiples de instrucción única (SIMT). Las unidades SIMT se ejecutan desde una unidad de instrucción sincronizada de transmisión única compartida. Los "registros vectoriales" son muy amplios y las tuberías tienden a ser largas. La parte de "roscado" de SIMT implica la forma en que se manejan los datos de forma independiente en cada una de las unidades informáticas.

Además, las GPU como Broadcom Videocore IV y otros procesadores vectoriales externos como el NEC SX-Aurora TSUBASA pueden utilizar menos unidades vectoriales de las que implica el ancho: en lugar de tener 64 unidades para un registro de 64 números de ancho, el hardware podría Haga un circuito canalizado de más de 16 unidades para un enfoque híbrido. El Broadcom Videocore IV también es capaz de este enfoque híbrido: indica nominalmente que su motor SIMD QPU admite operaciones de matriz FP de 16 largos en sus instrucciones, en realidad las realiza de 4 a la vez, como (otra) forma de "hilos". [dieciséis]

Ejemplo de instrucción vectorial

Este ejemplo comienza con un algoritmo ("IAXPY"), primero lo muestra en instrucciones escalares, luego en SIMD, luego en SIMD predicado y finalmente en instrucciones vectoriales. Esto ayuda gradualmente a ilustrar la diferencia entre un procesador vectorial tradicional y uno SIMD moderno. El ejemplo comienza con una variante entera de 32 bits de la función "DAXPY", en 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 ]; }                          

En cada iteración, cada elemento de y tiene un elemento de x multiplicado por a y sumado. El programa se expresa en forma lineal escalar para facilitar la lectura.

ensamblador escalar

La versión escalar de esto cargaría uno de cada uno de x e y, procesaría un cálculo, almacenaría un resultado y realizaría un bucle:

bucle: load32 r1 , x ; cargar uno de datos de 32 bits load32 r2 , y mul32 r1 , a , r1 ; r1 := r1 * a add32 r3 , r1 , r2 ; r3 := r1 + r2 almacenar32 r3 , y addl x , x , $4 ; x := x + 4 addl y , y , $4 subl n , n , $1 ; n := n - 1 jgz n , bucle ; bucle hacia atrás si n > 0 salida: ret                                       

El código similar al STAR sigue siendo conciso, pero debido a que la vectorización del STAR-100 se basó por diseño en el acceso a la memoria, ahora se requiere una ranura adicional de memoria para procesar la información. También se necesita el doble de latencia debido al requisito adicional de acceso a la memoria.

 ; Supongamos que tmp está preasignado vmul tmp , a , x , n ; tmp[i] = a * x[i] vadd y , y , tmp , n ; y[i] = y[i] + tmp[i] ret             

SIMD puro (no predicado, empaquetado)

Una arquitectura SIMD empaquetada moderna, conocida por muchos nombres (enumerados en la taxonomía de Flynn ), puede realizar la mayor parte de la operación en lotes. El código es mayoritariamente similar a la versión escalar. Se supone que tanto x como y están correctamente alineados aquí (solo comience en un múltiplo de 16) y que n es un múltiplo de 4, ya que de lo contrario se necesitaría algún código de configuración para calcular una máscara o ejecutar una versión escalar. También se puede suponer, por simplicidad, que las instrucciones SIMD tienen una opción para repetir automáticamente operandos escalares, como puede hacerlo ARM NEON. [17] Si no es así, se debe utilizar un "splat" (difusión) para copiar el argumento escalar a través de un registro SIMD:

 splatx4 v4 , a ; v4 = a,a,a,a   

El tiempo necesario sería básicamente el mismo que el de una implementación vectorial y = mx + cdescrita anteriormente.

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 ; volver si n > 0 salir: ret                                      

Tenga en cuenta que tanto los punteros x como y se incrementan en 16, porque esa es la longitud (en bytes) de cuatro enteros de 32 bits. Se tomó la decisión de que el algoritmo solo funcionará con SIMD de 4 anchos, por lo tanto, la constante está codificada en el programa.

Desafortunadamente para SIMD, la clave estaba en la suposición anterior, "que n es un múltiplo de 4", así como en el "acceso alineado", que, claramente, es un caso de uso especializado limitado.

De manera realista, para bucles de propósito general, como en bibliotecas portátiles, donde n no puede limitarse de esta manera, la sobrecarga de configuración y limpieza de SIMD para hacer frente a no múltiplos del ancho de SIMD, puede exceder con creces el recuento de instrucciones internas. el bucle mismo. Suponiendo que, en el peor de los casos, el hardware no pueda realizar accesos a la memoria SIMD desalineados, un algoritmo del mundo real:

El SIMD de ocho anchos requiere repetir el algoritmo de bucle interno primero con elementos SIMD de cuatro anchos, luego SIMD de dos anchos, luego uno (escalar), con una prueba y una bifurcación entre cada uno, para cubrir el primer y último SIMD restante. elementos (0 <= n <= 7).

Esto triplica con creces el tamaño del código; de hecho, en casos extremos, ¡da como resultado un aumento de un orden de magnitud en el recuento de instrucciones! Esto se puede demostrar fácilmente compilando el ejemplo iaxpy para AVX-512 , usando las opciones "-O3 -march=knl"de gcc .

Con el tiempo, a medida que ISA evoluciona para seguir aumentando el rendimiento, ISA Architects agrega SIMD de 2 anchos, luego SIMD de 4 anchos, luego SIMD de 8 anchos y más. Por tanto, se puede ver por qué AVX-512 existe en x86.

Sin predicción, cuanto mayor sea el ancho SIMD, peores serán los problemas, lo que provocará una proliferación masiva de códigos de operación, un rendimiento degradado, un consumo adicional de energía y una complejidad innecesaria del software. [18]

Por otro lado, los procesadores vectoriales están diseñados para realizar cálculos de longitud variable para un conteo arbitrario, n, y por lo tanto requieren muy poca configuración y ninguna limpieza. Incluso en comparación con los SIMD ISA que tienen máscaras (pero sin setvlinstrucciones), los procesadores vectoriales producen un código mucho más compacto porque no necesitan realizar un cálculo explícito de máscara para cubrir los últimos elementos (ilustrados a continuación).

SIMD predicado

Suponiendo un SIMD ISA hipotético predicado (con capacidad de máscara), y suponiendo nuevamente que las instrucciones SIMD pueden hacer frente a datos desalineados, el bucle de instrucciones se vería así:

vloop: # preparar máscara. pocas ISA tienen min aunque min t0 , n , $4 ; t0 = min(n, 4) desplazamiento m , $1 , t0 ; m = 1<<t0 sub m , m , $1 ; m = (1<<t0)-1 # ahora haga la operación, enmascarada por m bits 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 # actualiza x, y y n para el siguiente bucle addl x , t0 * 4 ; x := x + t0*4 addl y , t0 * 4 subl n , n , t0 ; n := n - t0 # bucle? jgz n , vloop ; volver si n > 0 salir: ret                                                            

Aquí se puede ver que el código es mucho más limpio pero un poco complejo: al menos, sin embargo, no hay configuración ni limpieza: en la última iteración del bucle, la máscara de predicado se establecerá en 0b0000, 0b0001, 0b0011, 0b0111 o 0b1111, lo que da como resultado que se realicen entre 0 y 4 operaciones de elementos SIMD, respectivamente. Una posible complicación adicional: algunos RISC ISA no tienen una instrucción "min", por lo que necesitan utilizar una rama o una comparación escalar predicada.

Está claro cómo el SIMD predicado merece al menos el término "capaz de vectores", porque puede hacer frente a vectores de longitud variable mediante el uso de máscaras de predicados. Sin embargo, el paso final hacia un ISA vectorial "verdadero" es no tener ninguna evidencia en el ISA de un ancho SIMD, dejando eso enteramente en manos del hardware.

Vector puro (verdadero) ISA

Para ISA vectoriales estilo Cray como RVV, se utiliza una instrucción llamada "setvl" (establecer longitud del vector). El hardware primero define cuántos valores de datos puede procesar en un "vector": podrían ser registros reales o un bucle interno (el enfoque híbrido, mencionado anteriormente). Esta cantidad máxima (el número de "carriles") de hardware se denomina "MVL" (longitud máxima del vector). Tenga en cuenta que, como se ve en SX-Aurora y Videocore IV, MVL puede ser una cantidad de carril de hardware real o virtual . (Nota: como se menciona en el tutorial ARM SVE2, los programadores no deben cometer el error de asumir un ancho de vector fijo: en consecuencia, MVL no es una cantidad que el programador necesite saber. Esto puede resultar un poco desconcertante después de años de mentalidad SIMD). [ tono ]

Al llamar a setvl con el número de elementos de datos pendientes a procesar, se permite (esencialmente necesario) que "setvl" limite eso a la longitud máxima del vector (MVL) y, por lo tanto, devuelva el número real que puede procesar el hardware en el vector posterior. instrucciones, y establece el registro especial interno, "VL", en esa misma cantidad. ARM se refiere a esta técnica como programación "independiente de la longitud del vector" en sus tutoriales sobre SVE2. [19]

A continuación se muestra el ensamblador de vectores estilo Cray para el mismo bucle de estilo SIMD, arriba. Tenga en cuenta que se utiliza t0 (que, al contener una copia conveniente de VL, puede variar) en lugar de constantes codificadas:

vloop: setvl t0 , n # VL=t0=min(MVL, n) vld32 v0 , x # cargar vector x vld32 v1 , y # cargar vector y vmadd32 v1 , v0 , a # v1 += v0 * a vst32 v1 , y # almacenar Y agregar y , t0 * 4 # avanzar y por VL*4 agregar x , t0 * 4 # avanzar x por VL*4 sub n , t0 # n -= VL (t0) bnez n , vloop # repetir si n! = 0                                     

Básicamente, esto no es muy diferente de la versión SIMD (procesa 4 elementos de datos por bucle) o de la versión Scalar inicial (procesa solo uno). n todavía contiene la cantidad de elementos de datos que quedan por procesar, pero t0 contiene la copia de VL: el número que se procesará en cada iteración. t0 se resta de n después de cada iteración, y si n es cero, entonces se han procesado todos los elementos.

Una serie de cosas a tener en cuenta al comparar con la variante de ensamblaje SIMD predicado:

  1. La setvlinstrucción tiene incorporada una mininstrucción.
  2. Cuando la variante SIMD codificó tanto el ancho (4) en la creación de la máscara como en el ancho SIMD (load32x4, etc.), los equivalentes vectoriales ISA no tienen tal límite. Esto hace que los programas vectoriales sean portátiles, independientes del proveedor y preparados para el futuro.
  3. La configuración de VL crea efectivamente una máscara de predicado oculta que se aplica automáticamente a los vectores.
  4. Mientras que con SIMD predicado la longitud de bits de la máscara está limitada a la que puede contener un registro escalar (o máscara especial), los registros de máscara vectorial ISA no tienen tal limitación. Los vectores Cray-I podrían tener poco más de 1.000 elementos (en 1977).

Así se puede ver, muy claramente, cómo los ISA vectoriales reducen el número de instrucciones.

También tenga en cuenta que, al igual que la variante SIMD predicada, los punteros a xey avanzan t0 multiplicado por cuatro porque ambos apuntan a datos de 32 bits, pero n se reduce directamente en t0. En comparación con el ensamblador SIMD de tamaño fijo, hay muy poca diferencia aparente: x e y avanzan mediante la constante codificada 16, n se reduce mediante un 4 codificado, por lo que inicialmente es difícil apreciar el significado. La diferencia viene en la comprensión de que el hardware vectorial podría ser capaz de realizar 4 operaciones simultáneas, o 64, o 10,000, sería exactamente el mismo ensamblador vectorial para todas ellas y aún no habría código de limpieza SIMD . Incluso en comparación con el SIMD con capacidad de predicado, es aún más compacto, más claro, más elegante y utiliza menos recursos.

No solo es un programa mucho más compacto (ahorra en el tamaño de la caché L1), sino que, como se mencionó anteriormente, la versión vectorial puede enviar mucho más procesamiento de datos a las ALU, lo que nuevamente ahorra energía porque la decodificación y emisión de instrucciones pueden permanecer inactivas.

Además, la cantidad de elementos que entran en la función puede comenzar en cero. Esto establece la longitud del vector en cero, lo que efectivamente desactiva todas las instrucciones del vector, convirtiéndolas en no operativas en tiempo de ejecución. Por lo tanto, a diferencia de SIMD no predicado, incluso cuando no hay elementos para procesar, no se desperdicia código de limpieza.

Ejemplo de reducción de vectores

Este ejemplo comienza con un algoritmo que implica reducción. Al igual que en el ejemplo anterior, primero se mostrará en instrucciones escalares, luego en SIMD y finalmente en instrucciones vectoriales, comenzando en c :

vacío ( size_t n , int a , const int x []) { int y = 0 ; para ( tamaño_t i = 0 ; i < n ; i ++ ) y += x [ i ]; devolver y ; }                          

Aquí, se utiliza un acumulador (y) para sumar todos los valores de la matriz, x.

ensamblador escalar

La versión escalar de esto cargaría cada uno de x, lo agregaría a y y realizaría un bucle:

 establecer y , 0 ; y inicializado a bucle cero: load32 r1 , x ; cargar un datos de 32 bits add32 y , y , r1 ; y := y + r1 addl x , x , $4 ; x := x + 4 subl n , n , $1 ; n := n - 1 jgz n , bucle ; bucle hacia atrás si n > 0 out: ret y ; devuelve resultado, y                             

Esto es muy sencillo. "y" comienza en cero, los enteros de 32 bits se cargan uno a la vez en r1, se suman a y, y la dirección de la matriz "x" pasa al siguiente elemento de la matriz.

reducción SIMD

Aquí es donde empiezan los problemas. SIMD por diseño es incapaz de realizar operaciones aritméticas "entre elementos". El elemento 0 de un registro SIMD se puede agregar al elemento 0 de otro registro, pero el elemento 0 no se puede agregar a nada que no sea otro elemento 0. Esto impone algunas limitaciones severas a las posibles implementaciones. Por simplicidad se puede suponer que n es exactamente 8:

 complemento r3 , x , $16 ; para el segundo 4 de x load32x4 v1 , x ; primeros 4 de x load32x4 v2 , r3 ; 2do 4 de x sumar32x4 v1 , v2 , v1 ; agregar 2 grupos                 

Hasta este punto se han realizado cuatro adiciones:

pero como un SIMD de 4 anchos es incapaz de agregar, por ejemplo, por diseñox[0]+x[1] , las cosas van rápidamente cuesta abajo, tal como lo hicieron con el caso general del uso de SIMD para bucles IAXPY de uso general. Para sumar los cuatro resultados parciales, se puede usar SIMD de dos anchos, seguido de una suma escalar única, para finalmente producir la respuesta, pero, con frecuencia, los datos deben transferirse fuera de registros SIMD dedicados antes de que se pueda realizar el último cálculo escalar. .

Incluso con un bucle general (n no fijo), la única forma de utilizar SIMD de 4 anchos es asumir cuatro "flujos" separados, cada uno de ellos compensado por cuatro elementos. Finalmente, hay que sumar los cuatro resultados parciales. Otras técnicas implican la reproducción aleatoria: se pueden encontrar ejemplos en línea para AVX-512 sobre cómo hacer "Suma horizontal" [20] [21]

Además del tamaño del programa y la complejidad, surge un problema potencial adicional si se trata de cálculos en punto flotante: el hecho de que los valores no se sumen en estricto orden (cuatro resultados parciales) podría provocar errores de redondeo.

Reducción de ISA vectorial

Los conjuntos de instrucciones vectoriales tienen operaciones de reducción aritmética integradas en la ISA. Si se supone que n es menor o igual a la longitud máxima del vector, solo se requieren tres instrucciones:

 setvl t0 , n # VL=t0=min(MVL, n) vld32 v0 , x # cargar vector x vredadd32 y , v0 # reducir-añadir en y           

El código cuando n es mayor que la longitud máxima del vector no es mucho más complejo y es un patrón similar al primer ejemplo ("IAXPY").

 establecer y , 0 vloop: setvl t0 , n # VL=t0=min(MVL, n) vld32 v0 , x # cargar vector x vredadd32 y , y , v0 # agregar todo x en y agregar x , t0 * 4 # avanzar x por VL*4 sub n , t0 # n -= VL (t0) bnez n , vloop # repetir si n != 0 ret y                             

La simplicidad del algoritmo es marcada en comparación con SIMD. Nuevamente, al igual que en el ejemplo de IAXPY, el algoritmo es independiente de la longitud (incluso en implementaciones integradas donde la longitud máxima del vector podría ser solo una).

Las implementaciones en hardware pueden, si están seguras de que se producirá la respuesta correcta, realizar la reducción en paralelo. Algunos ISA vectoriales ofrecen un modo de reducción paralela como una opción explícita, para cuando el programador sabe que cualquier posible error de redondeo no importa y que la baja latencia es fundamental. [22]

Este ejemplo nuevamente resalta una diferencia fundamental crítica entre los procesadores vectoriales verdaderos y los procesadores SIMD, incluidas la mayoría de las GPU comerciales, que están inspiradas en características de los procesadores vectoriales.

Perspectivas de ejemplos

En comparación con cualquier procesador SIMD que afirme ser un procesador vectorial, la reducción de orden de magnitud en el tamaño del programa es casi impactante. Sin embargo, este nivel de elegancia a nivel ISA tiene un precio bastante elevado a nivel de hardware:

  1. En el ejemplo de IAXPY, se puede ver que a diferencia de los procesadores SIMD, que pueden simplificar su hardware interno al evitar tener que lidiar con accesos a memoria desalineados, un procesador vectorial no puede salirse con la suya con tal simplificación: se escriben algoritmos que dependen inherentemente de la carga y el almacenamiento vectoriales. exitoso, independientemente de la alineación del inicio del vector.
  2. Si bien en el ejemplo de reducción se puede ver que, aparte de las instrucciones de permutación , SIMD por definición evita por completo las operaciones entre carriles (el elemento 0 solo se puede agregar a otro elemento 0), los procesadores vectoriales abordan esto de frente. Lo que los programadores se ven obligados a hacer en el software (usando la reproducción aleatoria y otros trucos para intercambiar datos en el "carril" correcto), los procesadores vectoriales deben hacerlo en el hardware, automáticamente.

En general, entonces existe la opción de tener

  1. Software complejo y hardware simplificado (SIMD)
  2. software simplificado y hardware complejo (procesadores vectoriales)

Estas marcadas diferencias son lo que distingue a un procesador vectorial de uno que tiene SIMD.

Características del procesador vectorial

Mientras que muchos SIMD ISA toman prestado o se inspiran en la lista siguiente, las características típicas que tendrá un procesador vectorial son: [23] [24] [25]

Funciones de procesamiento de vectores de GPU

Dado que muchas aplicaciones de sombreado 3D necesitan operaciones trigonométricas , así como vectores cortos para operaciones comunes (RGB, ARGB, XYZ, XYZW), la compatibilidad con lo siguiente suele estar presente en las GPU modernas, además de las que se encuentran en los procesadores vectoriales:

Falla (o falla) primero

Introducido en ARM SVE2 y RISC-V RVV está el concepto de cargas vectoriales secuenciales especulativas. ARM SVE2 tiene un registro especial llamado "Registro de primer fallo", [34] donde RVV modifica (trunca) la longitud del vector (VL). [35]

El principio básico de ffirst es intentar una carga vectorial secuencial grande, pero para permitir que el hardware trunque arbitrariamente la cantidad real cargada a la cantidad que tendría éxito sin generar una falla de memoria o simplemente a una cantidad (mayor que cero) que sea mas conveniente. El factor importante es que las instrucciones posteriores son notificadas o pueden determinar exactamente cuántas cargas realmente tuvieron éxito, utilizando esa cantidad para realizar el trabajo solo en los datos que realmente se han cargado.

Compare esta situación con SIMD, que es un ancho de carga fijo (inflexible) y un ancho de procesamiento de datos fijo, incapaz de hacer frente a cargas que cruzan los límites de las páginas, e incluso si lo fueran, no pueden adaptarse a lo que realmente tuvo éxito, pero, paradójicamente, Si el programa SIMD intentara siquiera descubrir de antemano (en cada bucle interno, cada vez) qué podría tener éxito de manera óptima, esas instrucciones sólo servirían para obstaculizar el rendimiento porque, por necesidad, serían parte del bucle interno crítico.

Esto comienza a insinuar la razón por la cual ffirst es tan innovador, y se ilustra mejor con memcpy o strcpy cuando se implementa con un SIMD estándar no predicado y no ffirst de 128 bits. Para IBM POWER9, el número de instrucciones optimizadas manualmente para implementar strncpy supera las 240. [36] Por el contrario, la misma rutina strncpy en el ensamblador RVV optimizado manualmente consta de apenas 22 instrucciones. [37]

El ejemplo de SIMD anterior podría potencialmente fallar y fallar al final de la memoria, debido a intentos de leer demasiados valores: también podría causar un número significativo de páginas o fallas desalineadas al cruzar de manera similar los límites. Por el contrario, al permitir a la arquitectura vectorial la libertad de decidir cuántos elementos cargar, la primera parte de un strncpy, si comienza inicialmente en un límite de memoria subóptimo, puede devolver cargas suficientes como para que en iteraciones posteriores del bucle el los lotes de lecturas de memoria vectorizadas están alineados de manera óptima con las cachés subyacentes y las disposiciones de memoria virtual. Además, el hardware puede optar por aprovechar la oportunidad para finalizar las lecturas de memoria de cualquier iteración de bucle determinada exactamente en el límite de una página (evitando una costosa segunda búsqueda de TLB), con una ejecución especulativa preparando la siguiente página de memoria virtual mientras los datos aún se están procesando en la página actual. bucle. Todo esto está determinado por el hardware, no por el programa en sí. [38]

Rendimiento y aceleración

Sea r la relación de velocidad del vector y f la relación de vectorización. Si el tiempo que le toma a la unidad vectorial sumar una matriz de 64 números es 10 veces más rápido que su contraparte escalar equivalente, r = 10. Además, si el número total de operaciones en un programa es 100, de las cuales solo 10 son escalares (después de la vectorización), entonces f = 0,9, es decir, el 90% del trabajo lo realiza la unidad vectorial. De ello se desprende la aceleración alcanzable de:

Entonces, incluso si el rendimiento de la unidad vectorial es muy alto ( ), hay una aceleración menor que , lo que sugiere que la relación f es crucial para el rendimiento. Esta relación depende de la eficiencia de la compilación como adyacencia de los elementos en memoria.

Ver también

Referencias

  1. ^ BN Malinovsky (1995). La historia de la tecnología informática en sus rostros (en ruso) . EQUIPO. ISBN 5770761318.
  2. ^ Grupo de investigación vertical MIAOW
  3. ^ GPU MIAOW
  4. ^ "Andes anuncia procesador vectorial RISC-V multinúcleo de 1024 bits: AX45MPV" (Presione soltar). Globo de noticias. 7 de diciembre de 2022 . Consultado el 23 de diciembre de 2022 .
  5. ^ Miyaoka, Y.; Choi, J.; Togawa, N.; Yanagisawa, M.; Ohtsuki, T. (2002). Un algoritmo de generación de unidades de hardware para la síntesis del núcleo del procesador con instrucciones tipo SIMD empaquetadas . Conferencia Asia-Pacífico sobre circuitos y sistemas. vol. 1. págs. 171-176. doi :10.1109/APCCAS.2002.1114930. hdl : 2065/10689 .
  6. ^ "Riscv-v-spec/V-spec.adoc en el maestro · riscv/Riscv-v-spec". GitHub . 16 de junio de 2023.
  7. ^ "Manual de referencia del lenguaje ensamblador del motor vectorial" (PDF) . 16 de junio de 2023.
  8. ^ "Documentación: desarrollador de Arm".
  9. ^ "Arquitectura vectorial". 27 de abril de 2020.
  10. ^ Procesadores vectoriales y SIMD, diapositivas 12 y 13
  11. ^ Procesamiento de matrices versus vectores, diapositivas 5 a 7
  12. ^ SIMD frente a GPU vectorial, diapositivas 22-24
  13. ^ Patterson, David A .; Hennessy, John L. (1998). Organización y diseño de computadoras: la interfaz hardware/software página 751-2 (2ª ed.). Morgan Kaufman. pag. 751-2. ISBN 155860491X.
  14. ^ "Riscv-v-spec/V-spec.adoc en el maestro · riscv/Riscv-v-spec". GitHub . 19 de noviembre de 2022.
  15. ^ Manual del programador de Videocore IV
  16. ^ Análisis de Videocore IV QPU por Jeff Bush
  17. ^ "Codificación para neón: multiplicación de matrices, parte 3". 11 de septiembre de 2013.
  18. ^ SIMD considerado dañino
  19. ^ Tutorial ARM SVE2
  20. ^ "Sse: transmisión de 1 a 4 y reducción de 4 a 1 en AVX-512".
  21. ^ "Ensamblaje: la forma más rápida de realizar una suma vectorial SSE horizontal (u otra reducción)".
  22. ^ "Riscv-v-spec/V-spec.adoc en el maestro · riscv/Riscv-v-spec". GitHub . 19 de noviembre de 2022.
  23. ^ Descripción general de Cray
  24. ^ RISC-V RVV ISA
  25. ^ Descripción general de SX-Arora
  26. ^ Instrucciones de recopilación y dispersión del registro RVV
  27. ^ "Procesador POWER10 de IBM: William Starke y Brian W. Thompto, IBM". YouTube . Archivado desde el original el 11 de diciembre de 2021.
  28. ^ Moreira, José E.; Barton, equipo; Batalla, Steven; Bergner, Peter; Bertrán, Ramón; Bhat, Puneeth; Caldeira, Pedro; Edelsohn, David; Fossum, Gordon; Frey, Brad; Ivanovic, Nemanja; Kerchner, Chip; Lim, Vicente; Kapoor, Shakti; Tulio Machado Filho; Silvia Melitta Mueller; Olsson, Brett; Sadasivam, Satish; Saleil, Bautista; Schmidt, Bill; Srinivasaraghavan, Rajalakshmi; Srivatsan, Shricharan; Thompson, Brian; Wagner, Andrés; Wu, Nelson (2021). "Una instalación matemática matricial para procesadores Power ISA (TM)". arXiv : 2104.03142 [cs.AR].
  29. ^ Krikelis, Anargyros (1996). "Un procesador modular masivamente paralelo para el procesamiento de visualización volumétrica". Computación de alto rendimiento para visualización y gráficos por computadora . págs. 101-124. doi :10.1007/978-1-4471-1011-8_8. ISBN 978-3-540-76016-0.
  30. ^ "Guía de programación CUDA C++".
  31. ^ LMUL > 1 en RVV
  32. ^ Patente estadounidense abandonada US20110227920-0096
  33. ^ QPU de vídeo núcleo IV
  34. ^ Introducción a ARM SVE2
  35. ^ Cargas RVV con falla primero
  36. ^ PATCH a libc6 para agregar strncpy POWER9 optimizado
  37. ^ Ejemplo de estructura RVV
  38. ^ Documento ARM SVE2 de N. Stevens