En informática , un procesador vectorial es una unidad central de procesamiento (CPU) que implementa un conjunto de instrucciones diseñadas para operar de manera eficiente y secuencial sobre grandes matrices unidimensionales de datos llamadas vectores . Cuando se integra como componente de hardware, el procesador vectorial suele denominarse unidad de procesamiento vectorial (VPU). Esto contrasta con los procesadores escalares , cuyas instrucciones operan únicamente sobre elementos de datos individuales, y con algunos de estos mismos procesadores escalares que cuentan con unidades aritméticas adicionales de instrucción única, datos múltiples (SIMD) o SIMD dentro de un registro (SWAR). Los procesadores vectoriales pueden mejorar considerablemente el rendimiento en ciertas cargas de trabajo, especialmente en simulación numérica , compresión y tareas similares. [ 1 ]
Las técnicas de procesamiento vectorial también se utilizan en el hardware de las consolas de videojuegos y en los aceleradores gráficos , pero invariablemente son de instrucción única, múltiples hilos (SIMT) y, ocasionalmente, de instrucción única, múltiples datos (SIMD).
Las máquinas vectoriales surgieron a principios de la década de 1970 y dominaron el diseño de supercomputadoras desde entonces hasta bien entrada la década de 1990, especialmente las diversas plataformas de Cray . La rápida caída de la relación precio-rendimiento de los diseños de microprocesadores convencionales provocó un declive en las supercomputadoras vectoriales durante la década de 1990.
Historia
Investigación y desarrollo iniciales
El desarrollo del procesamiento de matrices comenzó a principios de la década de 1960 en Westinghouse Electric Corporation con su proyecto Solomon . El objetivo de Solomon era aumentar drásticamente el rendimiento matemático mediante el uso de un gran número 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 aritmético-lógicas (ALU), una por ciclo, pero con un punto de datos diferente para que cada una trabajara. Esto permitió que la máquina Solomon aplicara un único algoritmo a un gran conjunto de datos , alimentado en forma de matriz, lo que la llevó a ser citada como un ejemplo de procesador de matrices en la taxonomía de Flynn .
En 1962, Westinghouse canceló el proyecto, pero la Universidad de Illinois en Urbana-Champaign lo retomó como ILLIAC IV . Su versión del diseño originalmente contemplaba una máquina de 1 GFLOPS con 256 ALUs, pero, cuando finalmente se entregó en 1972, solo tenía 64 ALUs y podía alcanzar entre 100 y 150 MFLOPS. Sin embargo, demostró que el concepto básico era sólido y, cuando se utilizó en aplicaciones intensivas en datos, como la dinámica de fluidos computacional , el ILLIAC fue la máquina más rápida del mundo. El enfoque del ILLIAC de usar ALUs separadas para cada elemento de datos no es común en diseños posteriores y a menudo se hace referencia a él dentro de una categoría separada de computación masivamente paralela : alrededor de 1972, Flynn clasificó este tipo de procesamiento como una forma temprana de instrucción única, hilos múltiples (SIMT).
International Computers Limited buscó evitar muchas de las dificultades con el concepto ILLIAC con su propio diseño de procesador de matriz distribuida (DAP), clasificando el ILLIAC y el DAP como procesadores de matriz celular que potencialmente ofrecían beneficios de rendimiento sustanciales sobre los diseños de procesadores vectoriales convencionales como el CDC STAR-100 y el Cray 1. [ 2 ]
Computadora para operaciones con funciones
Kartsev presentó y desarrolló en 1967 una computadora para operaciones con funciones. [ 3 ]
Supercomputadoras
Las primeras supercomputadoras vectoriales fueron la STAR-100 de Control Data Corporation y la Advanced Scientific Computer (ASC) de Texas Instruments , que se presentaron en 1974 y 1972, respectivamente.
La ALU básica ASC (es decir, de "una tubería") utilizaba una arquitectura de tubería que admitía cálculos escalares y vectoriales, con un rendimiento máximo de aproximadamente 20 MFLOPS, fácilmente alcanzable al procesar vectores largos. Las configuraciones de ALU ampliadas admitían "dos tuberías" o "cuatro tuberías", con una ganancia de rendimiento correspondiente de 2X o 4X. El ancho de banda de la memoria era suficiente para admitir estos modos ampliados.
La STAR-100 era más lenta que las supercomputadoras de la propia CDC, como la CDC 7600 , pero en tareas relacionadas con datos podía mantener el ritmo, siendo mucho más pequeña y económica. Sin embargo, la máquina también tardaba bastante en decodificar las instrucciones vectoriales y prepararse para ejecutar el proceso, por lo que requería conjuntos de datos muy específicos para poder trabajar con ellos antes de que se notara una mejora en la velocidad.
La técnica vectorial fue explotada por primera vez en su totalidad en 1976 por el famoso Cray-1 . En lugar de dejar los datos en memoria como el STAR-100 y el ASC, el diseño del Cray contaba con ocho registros vectoriales , cada uno con capacidad para sesenta y cuatro palabras de 64 bits. Las instrucciones vectoriales se aplicaban entre registros, lo que resulta mucho más rápido que acceder a la memoria principal. Mientras que el STAR-100 aplicaba una única operación a lo largo de un vector extenso en memoria y luego pasaba a la siguiente, el diseño del Cray cargaba una sección más pequeña del vector en los registros y luego aplicaba tantas operaciones como fuera posible a esos datos, evitando así muchas de las operaciones de acceso a memoria, que son mucho más lentas.
El diseño de Cray empleaba paralelismo de pipeline para implementar instrucciones vectoriales en lugar de múltiples ALU. Además, contaba con pipelines completamente independientes para distintas instrucciones; por ejemplo, la suma y la resta se implementaban en hardware diferente al de la multiplicación. Esto permitía que un lote de instrucciones vectoriales se distribuyera en cada una de las subunidades de la ALU, una técnica que denominaron encadenamiento vectorial . El Cray-1 tenía un rendimiento normal de unos 80 MFLOPS, pero con hasta tres cadenas en funcionamiento podía alcanzar un pico de 240 MFLOPS y un promedio de alrededor de 150, mucho más rápido que cualquier otra máquina de la época.

Otros ejemplos siguieron. Control Data Corporation intentó reingresar al mercado de gama alta con su máquina ETA-10 , pero se vendió mal y aprovecharon la 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 NEC introdujeron máquinas vectoriales basadas en registros similares a la Cray-1, que generalmente eran un poco más rápidas y mucho más pequeñas: la serie Fujitsu VP culminó con la VP2600, que ostentó el récord mundial de la supercomputadora más rápida en 1990-1991. Floating Point Systems (FPS), con sede en Oregón , construyó procesadores de matriz adicionales para minicomputadoras , y más tarde construyó sus propias minisupercomputadoras .
A lo largo de su historia, Cray continuó siendo líder en rendimiento, superando constantemente a la competencia con una serie de máquinas que culminaron en la Cray-2 , la Cray X-MP y la Cray Y-MP . Desde entonces, el mercado de supercomputadoras se ha centrado mucho más en el procesamiento masivamente paralelo que en mejores implementaciones de procesadores vectoriales. Sin embargo, reconociendo las ventajas del procesamiento vectorial, IBM desarrolló la Arquitectura Vectorial Virtual para su uso en supercomputadoras, acoplando varios procesadores escalares para que actuaran como un procesador vectorial. IBM también implementó el procesamiento vectorial al estilo Cray en la IBM 3090 con una función vectorial opcional .
Aunque las supercomputadoras vectoriales similares a la Cray-1 son menos populares hoy en día, NEC ha seguido fabricando este tipo de computadora hasta la actualidad con su serie SX . Más recientemente, la SX-Aurora TSUBASA integra el procesador y 24 o 48 gigabytes de memoria en un módulo HBM 2 dentro de una tarjeta que físicamente se asemeja a un coprocesador gráfico, pero en lugar de funcionar como tal, es la computadora principal, mientras que la computadora compatible con PC a la que está conectada proporciona funciones de soporte.
GPU
Las unidades de procesamiento gráfico ( GPU ) modernas incluyen una serie de pipelines de sombreado que pueden ser controlados por kernels de cómputo , y al ser generalmente SIMT, a menudo se las clasifica erróneamente como procesadores vectoriales, siendo ambas categorizaciones muy similares de SIMD en la taxonomía SIMD de Flynn de 1972. Las GPU utilizan una estrategia para ocultar las latencias de memoria cuando se topan con una forma extrema del muro 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-emisor 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 bancos de registros, sus propias unidades de carga/almacenamiento y sus propias cachés de datos L1 independientes. Por lo tanto, aunque todos los núcleos ejecutan simultáneamente la misma instrucción de forma sincronizada, lo hacen con datos completamente diferentes de ubicaciones de memoria completamente diferentes. Esto es significativamente más complejo y laborioso que "Packed SIMD" , que se limita estrictamente a la ejecución de operaciones aritméticas paralelas en pipeline únicamente. Aunque los detalles internos exactos de las GPU comerciales actuales son secretos de propiedad exclusiva, el equipo de MIAOW [ 4 ] pudo reunir información anecdótica suficiente para implementar un subconjunto de la arquitectura AMDGPU. [ 5 ]
Desarrollos recientes
Varias arquitecturas de CPU modernas se están diseñando 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 el Andes Technology AX45MPV. [ 6 ] 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 2016La mayoría de las CPU comerciales implementan arquitecturas con instrucciones SIMD de longitud fija. A primera vista, estas pueden considerarse una forma de procesamiento vectorial, ya que operan con múltiples conjuntos de datos (vectorizados y 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 propiamente dicho , 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 con predicados y procesamiento vectorial puro.
- SIMD puro (fijo) , también conocido como "SIMD empaquetado", [ 7 ] SIMD dentro de un registro (SWAR) y procesador segmentado en la taxonomía de Flynn. Ejemplos comunes que utilizan SIMD con características inspiradas en procesadores vectoriales incluyen: instrucciones MMX , SSE y AVX de Intel x86 , extensiones 3DNow! de AMD , ARM NEON , extensión VIS de Sparc , AltiVec de PowerPC , MSA de MIPS y el procesador Cell .
- SIMD con predicado : algunas implementaciones de SIMD admiten predicado por elemento , como ARM SVE2 y AVX-512.
- Los procesadores vectoriales puros , según la taxonomía de Duncan , incluyen el Cray-1 original , la serie C de Convex , el NEC SX , la serie VP de Fujitsu , el IBM 3090 Vector Facility y el RISC-V RVV . Si bien se basaban en memoria, tanto el TI ASC como el CDC STAR-100 eran procesadores vectoriales.
Otros diseños de CPU incluyen instrucciones múltiples para el procesamiento vectorial en conjuntos de datos múltiples (vectorizados), generalmente conocidos como MIMD (instrucciones múltiples, datos múltiples) y realizados con VLIW (palabras de instrucción muy largas) y EPIC (computación de instrucciones explícitamente paralelas). El Fujitsu FR-V , por ejemplo, combina características VLIW con predicación y SIMD empaquetado. [ 8 ]
Diferencia entre procesadores SIMD y procesadores vectoriales
Los conjuntos de instrucciones SIMD carecen de características cruciales en comparación con los conjuntos de instrucciones vectoriales. La más importante es que los procesadores vectoriales, por definición y diseño, siempre han sido diseñados para trabajar con vectores de longitud variable, sin estar restringidos a potencias de dos , desde su creación.
A menudo se afirma erróneamente que SIMD puro (de ancho fijo y sin predicado) es "vectorial" (porque SIMD procesa datos que resultan ser vectores). Un análisis detallado y una comparación de las arquitecturas de conjunto de instrucciones (ISA) históricas y modernas demuestran que las ISA vectoriales reales tienen una forma de establecer la longitud del vector en tiempo de ejecución, y a una cantidad que no es potencia de dos. En otras palabras, el número de elementos no está codificado de forma fija en la instrucción, como ocurre en la ISA SIMD. Esto se explica en el artículo "SIMD considerado perjudicial". [ 9 ]
- la instrucción original de Cray-1
VL, [ 10 ] - la
vsetvlinstrucción en RISC-V RVV, [ 11 ] - la
lvlinstrucción en NEC SX, [ 12 ] - En el IBM 3090 la instrucción equivalente se denominó
VLVCU[ 13 ]. - En sistemas de memoria a memoria como la serie CDC_Cyber#Cyber_200, la longitud del vector se codificaba parcialmente en registros de direcciones que contenían el punto de inicio en memoria del vector. El modelo Cyber 200 utilizaba los primeros 16 bits de una dirección de 64 bits para codificar la longitud del vector, tanto para los vectores de origen como para los de destino. [ 14 ]
Una característica clave adicional es el enmascaramiento predicado a nivel de elemento . En el modelo Cyber 200, la máscara de bits a nivel de elemento era en sí misma un vector almacenado en memoria. Algunos procesadores vectoriales tienen registros de máscara vectorial, como el Cray-1, que, al igual que el Cyber 200, utilizaba eficientemente un bit por elemento como máscara. Sin embargo, otras arquitecturas de conjunto de instrucciones vectoriales, como RISC-V, optaron por utilizar un registro del archivo de registros vectorial principal. [ 15 ]
Predicated SIMD (parte de la taxonomía de Flynn ) consiste en máscaras de predicado individuales y exhaustivas a nivel de elemento para cada elemento, como las disponibles actualmente en ARM SVE2 [ 16 ] y AVX-512 , pero ninguno de estos conjuntos de instrucciones incluye una instrucción explícita para "establecer la longitud del vector". Predicated SIMD utiliza ALUs SIMD de ancho fijo, pero permite la activación localmente controlada (predicada) de las unidades para simular vectores de longitud variable. Los ejemplos que se muestran a continuación ayudan a explicar estas distinciones categóricas.
Otra diferencia clave: SIMD, debido a que utiliza procesamiento por lotes de ancho fijo, no puede, por diseño, gestionar la iteración y la reducción. Esto se ilustra con más detalle en los ejemplos que se muestran a continuación.
encadenamiento vectorial

El encadenamiento es una técnica de rendimiento que permite utilizar salidas parciales de una operación vectorial como entradas para una operación posterior sin esperar a que la primera operación finalice. [ 17 ] La técnica fue utilizada por primera vez por Seymour Cray en la supercomputadora Cray 1 de 80 MHz en 1976. [ 6 ]
Descripción
Las computadoras pueden manipular uno o más datos a la vez, siguiendo un código máquina con significados predefinidos (un conjunto de instrucciones ). Una instrucción de suma puede, por ejemplo, realizar lo que esencialmente es "sumar A a B y colocar el resultado en C". Los datos para A, B y C podrían estar —al menos en teoría— codificados 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 obtener los datos de la memoria lleva cierto tiempo, durante el cual la CPU tradicionalmente permanecía inactiva esperando a que aparecieran los datos solicitados. A medida que la velocidad de la CPU ha aumentado, esta latencia de memoria se ha convertido históricamente en un gran impedimento para el rendimiento; véase Memoria de acceso aleatorio § Muro de memoria .
Para reducir el tiempo que consumen estos pasos, la mayoría de las CPU modernas utilizan una técnica conocida como segmentación de instrucciones, en la que las instrucciones pasan por varias subunidades sucesivamente. La primera subunidad lee la dirección y la decodifica, la siguiente obtiene los valores en esas direcciones y la siguiente realiza el cálculo. Con la segmentación, el truco consiste en comenzar a decodificar la siguiente instrucción incluso antes de que la primera haya salido de la CPU, como en una cadena de montaje , de modo que el decodificador de direcciones se utiliza constantemente. Cualquier instrucción en particular tarda el mismo 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 eficiente que si lo hiciera una a una.
Los procesadores vectoriales llevan este concepto un paso más allá. En lugar de segmentar solo las instrucciones, segmentan también los datos. El procesador recibe instrucciones que no solo indican sumar A a B, sino también sumar todos los números "desde aquí hasta aquí" a todos los números "desde allí hasta allí". En lugar de tener que decodificar constantemente las instrucciones y luego obtener los datos necesarios para completarlas, el procesador lee una sola instrucción de la memoria, y la definición de la instrucción implica que esta operará nuevamente con otro dato, en una dirección un incremento mayor que la anterior. Esto maximiza significativamente la efectividad de las instrucciones.
Para ilustrar la diferencia que esto puede suponer, consideremos la sencilla tarea de sumar dos grupos de 10 números. En un lenguaje de programación convencional, se escribiría un bucle que tomara cada par de números por turno y luego los sumara. 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 ; suma 10 números en a a 10 números en b, almacena los resultados en 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 on add b , b , $4 add c , c , $4 dec count ; decrement jnez count , loop ; loop back if count is yet not 0 retPero para un procesador vectorial, esta tarea se ve considerablemente diferente:
; supongamos que tenemos registros vectoriales v1-v3 ; con un tamaño igual o mayor que 10 move $10 , count ; count = 10 vload v1 , a , count vload v2 , b , count vadd v3 , v1 , v2 vstore v3 , c , count retNótese la ausencia total de bucles en las instrucciones, ya que es el hardware el que ha realizado 10 operaciones secuenciales: en la práctica, el recuento de bucles se basa en una instrucción específica .
Las arquitecturas de conjunto de instrucciones vectoriales al estilo Cray van un paso más allá y proporcionan un registro global de "conteo", llamado longitud del vector (VL):
; nuevamente supongamos que tenemos registros vectoriales v1-v3 ; con un tamaño mayor o igual a 10 setvli $10 # Establecer la longitud del vector VL=10 vload v1 , a # 10 cargas desde a vload v2 , b # 10 cargas desde b vadd v3 , v1 , v2 # 10 sumas vstore v3 , c # 10 almacenamientos en c retEste enfoque conlleva varios ahorros inherentes. [ 18 ]
- Solo se necesitan tres traducciones de direcciones. Dependiendo de la arquitectura, esto puede representar un ahorro significativo por sí solo.
- Otro ahorro reside en la obtención y decodificación de la instrucción en sí, que solo tiene que hacerse una vez en lugar de diez.
- 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 en el tamaño de la caché de instrucciones L1 y una reducción en el consumo de energía.
- Al reducirse el tamaño del programa, la predicción de bifurcaciones tiene una tarea más sencilla.
- Al no estar la longitud (equivalente al ancho SIMD) codificada directamente en la instrucción, la codificación no solo es más compacta, sino que también es "a prueba de futuro" y permite que incluso los diseños de procesadores integrados consideren el uso de vectores simplemente para obtener todas las demás ventajas, en lugar de buscar un alto rendimiento.
Además, en las arquitecturas de conjunto de instrucciones (ISA) de procesadores vectoriales más modernas, se ha introducido la opción "Fail on First" o "Fault First" (véase más abajo), lo que aporta aún más ventajas.
Pero, además, un procesador vectorial de alto rendimiento puede tener múltiples unidades funcionales sumando esos números en paralelo. No es necesario comprobar las 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 bloqueos. De este modo, las operaciones matemáticas se completan mucho más rápido en general, siendo el factor limitante el tiempo necesario para obtener los datos de la memoria.
No todos los problemas pueden abordarse con este tipo de solución. Incluir este tipo de instrucciones necesariamente aumenta la complejidad de la CPU. Esta complejidad suele ralentizar la ejecución de otras instrucciones, por ejemplo, cuando no se trata de sumar 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 puede mitigarse en cierta medida manteniendo todo el conjunto de instrucciones (ISA) según los principios de RISC : RVV solo añade alrededor de 190 instrucciones vectoriales, incluso con las funciones avanzadas. [ 19 ] )
Los procesadores vectoriales se diseñaron tradicionalmente para funcionar de manera óptima solo con grandes cantidades de datos. Por esta razón, este tipo de CPU se encontraban principalmente en supercomputadoras , ya que estas, en general, se ubicaban en lugares como centros de predicción meteorológica y laboratorios de física, donde se procesaban enormes volúmenes de datos. Sin embargo, como se ha demostrado anteriormente, la eficiencia de las arquitecturas de conjunto de instrucciones vectoriales (ISA) aporta otras ventajas que resultan atractivas incluso para aplicaciones embebidas.
Instrucciones vectoriales
El ejemplo de pseudocódigo vectorial anterior parte de la suposición de que la computadora vectorial puede procesar más de diez números en un solo lote. Para una mayor cantidad de números en el registro vectorial, resulta inviable que la computadora disponga de un registro tan grande. En consecuencia, el procesador vectorial adquiere la capacidad de realizar bucles por sí mismo o expone algún tipo de registro de control (estado) vectorial al programador, generalmente conocido como longitud vectorial.
Las instrucciones autorreplicantes 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, solo se pueden realizar cálculos muy simples de manera efectiva en hardware de esta forma sin un aumento de costos muy grande. Dado que todos los operandos deben estar en memoria para la arquitectura STAR-100, la latencia causada por el acceso también se volvió enorme.
Broadcom incluyó espacio en todas las operaciones vectoriales de la arquitectura Videocore IV para un REPcampo, pero a diferencia del STAR-100, que utiliza memoria para sus repeticiones, las repeticiones de Videocore IV están presentes en todas las operaciones, incluidas las operaciones vectoriales aritméticas. La longitud de la repetición puede ser un rango pequeño de potencia de dos o provenir de uno de los registros escalares. [ 20 ]
El Cray-1 introdujo la idea de usar registros del procesador para almacenar datos vectoriales en lotes. La longitud de los lotes (longitud del vector, VL) se podía configurar dinámicamente con una instrucción especial, cuya importancia con respecto a Videocore IV (y, como se mostrará más adelante, también a SIMD) radicaba en que la longitud de repetición no tenía que formar parte de la codificación de la instrucción. De esta forma, se podía realizar mucho más trabajo en cada lote; además, la codificación de la instrucción era mucho más elegante y compacta. El único inconveniente era que, para aprovechar al máximo esta capacidad adicional de procesamiento por lotes, la carga de memoria y la velocidad de almacenamiento también debían aumentar. A veces se afirma que esto es una desventaja de los procesadores vectoriales tipo Cray, y la serie Fujitsu VP cometió este error: en realidad, forma parte de la consecución de un alto rendimiento, como se observa en las GPU , que se enfrentan exactamente al mismo problema.
Los ordenadores SIMD modernos pretenden superar a los primeros Cray mediante el uso directo de múltiples ALU, logrando un mayor grado de paralelismo en comparación con el uso exclusivo de la arquitectura escalar en paralelo. Los procesadores vectoriales modernos (como el SX-Aurora TSUBASA ) combinan ambos enfoques, enviando múltiples datos a múltiples ALU SIMD internas en paralelo, cuyo número es seleccionado dinámicamente por el programa vectorial en tiempo de ejecución. Se pueden usar máscaras para cargar y almacenar datos selectivamente en ubicaciones de memoria, y también para deshabilitar selectivamente el procesamiento de elementos de las ALU SIMD. Algunos procesadores con SIMD ( AVX-512 , ARM SVE2 ) son capaces de este tipo de procesamiento selectivo por elemento ( "predicado" ), y son estos los que merecen la denominación de "procesador vectorial" o, al menos, la afirmación de ser capaces de realizar "procesamiento vectorial". Los procesadores SIMD sin predicado por elemento ( MMX , SSE , AltiVec ) no lo son en absoluto.
Las GPU modernas, que cuentan con numerosas unidades de cómputo pequeñas, cada una con su propia ALU SIMD independiente, utilizan el algoritmo SIMT ( Single Instruction Multiple Threads ). Las unidades SIMT se ejecutan desde una única unidad de instrucción compartida y sincronizada por difusión. Los registros vectoriales son muy amplios y las tuberías tienden a ser largas. La parte de "hilo" de SIMT se refiere a la forma en que se gestionan los datos de forma independiente en cada una de las unidades de cómputo.
Además, las GPU como la Broadcom Videocore IV y otros procesadores vectoriales externos como el NEC SX-Aurora TSUBASA pueden usar menos unidades vectoriales de lo que implica el ancho: en lugar de tener 64 unidades para un registro de 64 números de ancho, el hardware podría realizar un bucle segmentado sobre 16 unidades para un enfoque híbrido. La Broadcom Videocore IV también es capaz de este enfoque híbrido: aunque nominalmente indica que su motor SIMD QPU admite operaciones de matriz FP de 16 longitudes en sus instrucciones, en realidad las realiza de 4 en 4, como (otra) forma de "hilos". [ 21 ]
Ejemplo de instrucción vectorial
Este ejemplo comienza con un algoritmo ("IAXPY"), primero lo muestra en instrucciones escalares, luego en SIMD, luego en SIMD con predicados y finalmente en instrucciones vectoriales. Esto ayuda a ilustrar de forma incremental la diferencia entre un procesador vectorial tradicional y uno moderno con SIMD. 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, a cada elemento de y se le suma un elemento de x multiplicado por a. El programa se expresa en forma lineal escalar para facilitar su lectura.
Ensamblador escalar
La versión escalar de esto cargaría un valor de cada uno de los valores x e y, procesaría un cálculo, almacenaría un resultado y repetiría el proceso:
bucle: load32 r1 , x ; cargar un dato de 32 bits 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 ; volver al bucle si n > 0 out: retEl código, similar al de STAR, sigue siendo conciso, pero dado que la vectorización del STAR-100 se diseñó en torno a accesos a memoria, ahora se requiere una ranura de memoria adicional para procesar la información. Además, se necesita el doble de latencia debido a este mayor 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] retSIMD puro (sin aditivos, envasado)
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 en su mayor parte similar a la versión escalar. Se supone que tanto x como y están correctamente alineados aquí (solo comienzan 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 para 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. [ 22 ] Si no la tiene, se debe usar un "splat" (difusión) para copiar el argumento escalar a través de un registro SIMD:
splatx4 v4 , a ; v4 = a,a,a,aEl tiempo empleado 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 out: retNótese que los punteros x e y se incrementan en 16, ya que esa es la longitud (en bytes) de cuatro enteros de 32 bits. Se decidió que el algoritmo solo trabajaría con SIMD de 4 bits, por lo que la constante está codificada directamente 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 y limitado.
En la práctica, para bucles de propósito general como los de las bibliotecas portátiles, donde n no se puede limitar de esta manera, la sobrecarga de configuración y limpieza para SIMD, con el fin de lidiar con anchos de memoria que no son múltiplos del ancho de SIMD, puede superar con creces el número de instrucciones dentro del propio bucle. Suponiendo en el peor de los casos que el hardware no pueda realizar accesos a memoria SIMD no alineados, un algoritmo real hará lo siguiente:
- Primero, se requiere una sección preparatoria que procese los datos iniciales no alineados, hasta el punto en que las operaciones SIMD alineadas con la memoria puedan tomar el control. Esto implicará operaciones escalares (más lentas) u operaciones SIMD empaquetadas de menor tamaño. Cada copia implementa el bucle interno completo del algoritmo.
- realizar el bucle SIMD alineado con el ancho SIMD máximo hasta los últimos elementos (los restantes que no se ajustan al ancho SIMD fijo)
- Cuenta con una fase de limpieza que, al igual que la sección preparatoria, es igual de extensa y compleja.
El algoritmo SIMD de ocho elementos requiere repetir primero el algoritmo del bucle interno con elementos SIMD de cuatro elementos, luego con elementos SIMD de dos elementos y luego con uno (escalar), con una prueba y una bifurcación entre cada uno, para cubrir los primeros y últimos elementos SIMD restantes (0 <= n <= 7).
Esto triplica con creces el tamaño del código; de hecho, en casos extremos, ¡resulta en un aumento de un orden de magnitud en el número de instrucciones! Esto se puede demostrar fácilmente compilando el ejemplo iaxpy para AVX-512 , utilizando las opciones "-O3 -march=knl"de gcc .
Con el tiempo, a medida que la arquitectura de conjunto de instrucciones (ISA) evoluciona para mejorar su rendimiento, los arquitectos de ISA añaden SIMD de 2 núcleos, luego de 4 núcleos, de 8 núcleos y así sucesivamente. Por lo tanto, se entiende por qué existe AVX-512 en la arquitectura x86.
Sin predicción, cuanto mayor sea el ancho SIMD, peores serán los problemas, lo que conlleva una proliferación masiva de códigos de operación, un rendimiento degradado, un mayor consumo de energía y una complejidad de software innecesaria. [ 23 ]
Por otro lado, los procesadores vectoriales están diseñados para realizar cálculos de longitud variable para un número arbitrario de elementos, n, por lo que requieren muy poca configuración y ninguna limpieza. Incluso comparados con las arquitecturas SIMD que utilizan máscaras (pero no setvlinstrucciones), los procesadores vectoriales generan un código mucho más compacto, ya que no necesitan realizar cálculos explícitos de máscara para cubrir los últimos elementos (como se ilustra a continuación).
SIMD predicho
Suponiendo una hipotética arquitectura SIMD predicada (con capacidad de máscara) y asumiendo nuevamente que las instrucciones SIMD pueden manejar 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) shift m , $1 , t0 ; m = 1<<t0 sub m , m , $1 ; m = (1<<t0)-1 # ahora hacer 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 # actualizar 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 out: retAquí 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 resulta en que se realicen entre 0 y 4 operaciones de elementos SIMD, respectivamente. Una complicación potencial adicional: algunas ISA RISC no tienen una instrucción "min", por lo que necesitan usar en su lugar una comparación de predicado de salto o escalar.
Es evidente que la arquitectura SIMD basada en predicados merece, como mínimo, el término "capaz de manejar vectores", ya que puede gestionar vectores de longitud variable mediante el uso de máscaras de predicados. Sin embargo, el paso final hacia una arquitectura de conjunto de instrucciones (ISA) vectorial "auténtica" consiste en eliminar por completo cualquier indicio de ancho de banda SIMD en la propia ISA, dejando esta función totalmente en manos del hardware.
ISA vectorial puro (verdadero)
Para las arquitecturas de conjunto de instrucciones vectoriales tipo Cray, como RVV, se utiliza una instrucción llamada " setvl " (establecer longitud del vector). El hardware define primero cuántos valores de datos puede procesar en un "vector": esto puede 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). Cabe destacar que, como se observa en SX-Aurora y Videocore IV, MVL puede ser una cantidad real de carriles de hardware o una virtual . (Nota: Como se menciona en el tutorial de ARM SVE2, los programadores no deben cometer el error de asumir un ancho de vector fijo; por lo tanto, MVL no es una cantidad que el programador necesite conocer. Esto puede resultar un poco desconcertante después de años de mentalidad SIMD).
Al llamar a `setvl` con el número de elementos de datos pendientes de procesar, se permite (y es esencial) que `setvl` limite dicho número a la Longitud Máxima del Vector (MVL) y, por lo tanto, devuelve el número real que el hardware puede procesar en las instrucciones vectoriales subsiguientes, y establece el registro especial interno, `VL`, con ese mismo valor. ARM se refiere a esta técnica como programación "independiente de la longitud del vector" en sus tutoriales sobre SVE2. [ 24 ]
A continuación se muestra el ensamblador vectorial estilo Cray para el mismo bucle estilo SIMD que se muestra arriba. Nótese que t0 (que, al contener una copia conveniente de VL, puede variar) se utiliza 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 add y , t0 * 4 # avanzar y en VL*4 add x , t0 * 4 # avanzar x en VL*4 sub n , t0 # n -= VL (t0) bnez n , vloop # repetir si n != 0En esencia, no difiere mucho de la versión SIMD (que procesa 4 elementos de datos por iteración si MVL es 4) ni de la versión escalar inicial (que procesa solo uno). n sigue conteniendo el número de elementos de datos que quedan por procesar, pero t0 contiene la copia de VL, es decir, 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, significa que se han procesado todos los elementos.
Hay varios aspectos a tener en cuenta al compararlo con la variante de ensamblaje SIMD prevista:
- La
setvlinstrucción contiene unamininstrucción - Mientras que la variante SIMD codificaba el ancho (4) tanto en la creación de la máscara como en el ancho SIMD (load32x4, etc.), los equivalentes de la arquitectura vectorial no tienen tal limitación. Esto hace que los programas vectoriales sean portátiles, independientes del proveedor y a prueba de futuro.
- Configurar VL crea efectivamente una máscara de predicado oculta que se aplica automáticamente a los vectores.
- Mientras que con SIMD basado en predicados la longitud de bits de la máscara está limitada a la que puede almacenarse en un registro escalar (o de máscara especial), los registros de máscara de la arquitectura vectorial no tienen tal limitación. Los vectores de Cray-I podían tener poco más de 1000 elementos (en 1977).
De este modo, se puede observar con total claridad cómo las arquitecturas de conjunto de instrucciones vectoriales reducen el número de instrucciones.
Cabe destacar que, al igual que en la variante SIMD con predicados, los punteros a x e y se incrementan en t0 cuatro veces porque ambos apuntan a datos de 32 bits, pero n se decrementa directamente en t0. En comparación con el ensamblador SIMD de tamaño fijo, la diferencia aparente es mínima: x e y se incrementan en la constante 16, y n se decrementa en 4, también de forma fija, por lo que inicialmente resulta difícil apreciar su importancia. La diferencia radica en comprender que el hardware vectorial podría realizar 4, 64 o 10 000 operaciones simultáneas; el ensamblador vectorial sería exactamente el mismo para todas ellas y no habría código de limpieza SIMD . Incluso comparado con el SIMD con capacidad para predicados, sigue siendo más compacto, claro, elegante y consume menos recursos.
No solo es un programa mucho más compacto (ahorrando espacio en 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, el número de elementos que entran en la función puede comenzar en cero. Esto establece la longitud del vector en cero, lo que desactiva todas las instrucciones vectoriales, convirtiéndolas en operaciones nulas en tiempo de ejecución. Por lo tanto, a diferencia de SIMD sin predicados, incluso cuando no hay elementos que procesar, no hay código de limpieza innecesario ni preámbulo: ni siquiera cuando n=0.
Instalación IBM 370 Vector
En el IBM 3090 se logró un beneficio adicional (una mayor reducción en el número de instrucciones). [ 25 ]
VLVCU GR4 # Cargar VCT, actualizar GR4 y CCode vloop: vld32 v0 , x , v0 # cargar vector x, actualizar v0 vld32 v1 , y # cargar vector y (sin actualizar v1) vmadd32 v1 , v0 , a # v1 += v0 * a vst32 v1 , y , v1 # almacenar Y, ahora actualizar v1 VLVCU GR4 # Cargar VCT, actualizar GR4 y CCode BC 3 , vloop # Volver a la rama si VCT>0La VLVCUinstrucción no solo establecería la longitud del conteo vectorial ( VCT), sino que también restaría la nueva longitud vectorial del registro escalar y actualizaría un código de condición que la bifurcación podría comprobar. En efecto, VLVCUcombinaba dos (tres si se incluye la configuración del código de condición) en una:
# IBM 370 VLVCU hace lo siguiente: setvl GR4 # VL=min(MVL, GR4) sub GR4 , VL # n -= VL (GR4), también establece CCAdemás, tanto las instrucciones Vector-Load como Vector-Store podían actualizar los registros de direcciones, ahorrando instrucciones explícitas para realizar cálculos de direcciones que, en efecto , ya se habían realizado de forma interna al cargar/almacenar cada elemento del vector.
En Libre-SOC se utiliza exactamente el mismo principio , ilustrado con un ejemplo DAXPY (punto flotante de 64 bits): [ 26 ]
# r5: contador n; r6: puntero x; r7: puntero y; fp1: a mtctr 5 # mover n a CTR vloop: setvl MAXVL = 32 , VL = CTR # en realidad VL=MIN(MAXVL,CTR) # DAXPY es de 8 bytes, por lo tanto 8(r6) y 8(r7) sv.lfdup * 32 , 8 ( r6 ) # cargar x en fp32-63, incrementar x sv.lfd / els * 64 , 8 ( r7 ) # cargar y en fp64-95, SIN INC sv.fmadd * 64 ,* 64 , 1 ,* 32 # (*y) = (*y) * (*x) + a sv.stfdup * 64 , 8 ( r7 ) # almacenar en y, post-incrementar y sv.bc / ctr vloop # decrementar CTR en VL, ¡salto! ceroSe añaden modos de posactualización a las versiones vectorizadas de Load-with-Update y Store-with-Update, lo que evita la necesidad de una instrucción independiente para el cálculo de direcciones cuando los procesos internos de Load/Store de la CPU ya lo han realizado. Cabe destacar que, al igual que en el ejemplo del IBM 370, la dirección del vector y se actualiza mediante Store-with-Update, no mediante Load-with-Update. El registro CTR de Power ISA se utiliza para el recuento total en lugar de un registro escalar, y la sv.bc/CTRinstrucción realiza la reducción de CTR por la longitud del vector (VL) actual, seguida de una comprobación de CTR para ver si es cero y una bifurcación si no lo es.
El enfoque es diferente, pero logra el mismo resultado final que el IBM 3090: una compactación significativa de instrucciones que ya se consideran altamente compactas. [ 27 ] Un ejemplo de bucle interno de RISC-V Vector DAXPY tiene 10 instrucciones, mientras que Libre-SOC e IBM 370, como se muestra arriba, tienen 6: una reducción del 40 %.
Ejemplo de reducción vectorial
Este ejemplo comienza con un algoritmo que implica reducción. Al igual que en el ejemplo anterior, se mostrará primero en instrucciones escalares, luego SIMD y finalmente en instrucciones vectoriales, comenzando en c :
void ( size_t n , int a , const int x []) { int y = 0 ; for ( size_t i = 0 ; i < n ; i ++ ) y += x [ i ]; return y ; }Aquí, se utiliza un acumulador (y) para sumar todos los valores en el array x.
Ensamblador escalar
La versión escalar de esto cargaría cada uno de los x, lo sumaría a y y repetiría el proceso:
set y , 0 ; y inicializado a cero loop: load32 r1 , x ; cargar un dato 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 , loop ; volver al bucle if n > 0 out: ret y ; devuelve el resultado, yEsto es muy sencillo. "y" comienza en cero, los enteros de 32 bits se cargan uno a uno en r1, se suman a y, y la dirección del array "x" se mueve al siguiente elemento del array.
reducción SIMD
Aquí es donde comienzan los problemas. Por diseño, SIMD es incapaz de realizar operaciones aritméticas "entre elementos". El elemento 0 de un registro SIMD se puede sumar al elemento 0 de otro registro, pero el elemento 0 no se puede sumar a nada que no sea otro elemento 0. Esto impone serias limitaciones a las posibles implementaciones. Para simplificar, se puede asumir que n es exactamente 8:
addl r3 , x , $16 ; para el segundo 4 de x load32x4 v1 , x ; primer 4 de x load32x4 v2 , r3 ; segundo 4 de x add32x4 v1 , v2 , v1 ; agregar 2 gruposEn este punto se han realizado cuatro adiciones:
x[0]+x[4]- Primera operación SIMD ADD: el elemento 0 del primer grupo se agrega al elemento 0 del segundo grupo.x[1]+x[5]- Segunda operación SIMD ADD: el elemento 1 del primer grupo se agrega al elemento 1 del segundo grupo.x[2]+x[6]- Tercera operación SIMD ADD: el elemento 2 del primer grupo se agrega al elemento 2 del segundo grupo.x[3]+x[7]- Cuarta operación SIMD ADD: el elemento 3 del primer grupo se agrega al elemento 3 del segundo grupo.
Sin embargo, dado que la instrucción SIMD de 4 bits no puede realizar sumas por diseñox[0]+x[1] , por ejemplo, la situación se complica rápidamente, al igual que sucedió con el caso general del uso de SIMD para bucles IAXPY de propósito general. Para sumar los cuatro resultados parciales, se puede usar SIMD de dos bits, seguido de una suma escalar simple, para finalmente obtener la respuesta. No obstante, con frecuencia, los datos deben transferirse desde 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 usar SIMD de 4 vías es asumir cuatro "flujos" separados, cada uno desplazado por cuatro elementos. Finalmente, los cuatro resultados parciales deben sumarse. Los problemas se agravan a medida que aumenta el ancho del hardware SIMD (se introduce un conjunto de instrucciones completamente nuevo) y, peor aún, algunas instrucciones para realizar la suma horizontal, como AMD XOP_instruction_set , se eliminaron después de unos años. Algunas técnicas implican la mezcla: se pueden encontrar ejemplos en línea que requieren un conocimiento muy detallado para AVX-512 y SSE sobre cómo realizar la "suma horizontal" [ 28 ] [ 29 ].
Además del tamaño del programa y su complejidad, surge un problema potencial adicional si se utilizan cálculos de punto flotante: el hecho de que los valores no se sumen en un orden estricto (cuatro resultados parciales) podría dar lugar a errores de redondeo.
Reducción de Vector ISA
Los conjuntos de instrucciones vectoriales tienen operaciones de reducción aritmética integradas en el 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-agregar en yEl código cuando n es mayor que la longitud máxima del vector no es mucho más complejo y sigue un patrón similar al del primer ejemplo ("IAXPY").
set y , 0 vloop: setvl t0 , n # VL=t0=min(MVL, n) vld32 v0 , x # cargar vector x vredadd32 y , y , v0 # sumar todos los x en y add x , t0 * 4 # avanzar x en VL*4 sub n , t0 # n -= VL (t0) bnez n , vloop # repetir si n != 0 ret yLa simplicidad del algoritmo es notable en comparación con SIMD. 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 puede ser solo uno).
Las implementaciones en hardware pueden, si tienen la certeza de que se producirá la respuesta correcta, realizar la reducción en paralelo. Algunas arquitecturas de conjunto de instrucciones vectoriales ofrecen un modo de reducción paralela como opción explícita, para cuando el programador sabe que los posibles errores de redondeo no importan y la baja latencia es fundamental. [ 30 ]
Este ejemplo vuelve a poner de relieve una diferencia fundamental clave entre los verdaderos procesadores vectoriales y aquellos procesadores SIMD, incluidas la mayoría de las GPU comerciales, que están inspirados en características de los procesadores vectoriales.
Ideas extraídas de ejemplos
En comparación con cualquier procesador SIMD que se autodenomine procesador vectorial, la reducción de tamaño de programa es casi sorprendente. Sin embargo, este nivel de sofisticación a nivel de conjunto de instrucciones tiene un precio bastante elevado a nivel de hardware:
- A partir del ejemplo de IAXPY, se puede observar que, a diferencia de los procesadores SIMD, que pueden simplificar su hardware interno evitando lidiar con el acceso a memoria desalineada, un procesador vectorial no puede permitirse tal simplificación: se escriben algoritmos que dependen intrínsecamente de que la carga y el almacenamiento del vector sean exitosos, independientemente de la alineación del inicio del vector.
- Si bien en el ejemplo de reducción se observa 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 sumar a otro elemento 0), los procesadores vectoriales abordan este problema directamente. Lo que los programadores se ven obligados a hacer en el software (utilizando la función de mezcla y otros trucos para intercambiar datos en el "carril" correcto), los procesadores vectoriales deben hacerlo automáticamente en el hardware.
En general, entonces hay una opción para tener
- Software complejo y hardware simplificado (SIMD)
- Software simplificado y hardware complejo (procesadores vectoriales)
Estas marcadas diferencias son las que distinguen a un procesador vectorial de uno que utiliza SIMD.
Características del procesador vectorial
Si bien muchas ISA SIMD toman prestadas o se inspiran en la siguiente lista, las características típicas que tendrá un procesador vectorial son: [ 31 ] [ 32 ] [ 33 ]
- Carga y almacenamiento vectorial : las arquitecturas vectoriales con un diseño de registro a registro (análogo a las arquitecturas de carga y almacenamiento de los procesadores escalares) disponen de instrucciones para transferir múltiples elementos entre la memoria y los registros vectoriales. Normalmente, admiten varios modos de direccionamiento.
- El modo de direccionamiento de paso unitario es esencial: los elementos son contiguos.
- Las arquitecturas vectoriales modernas también suelen admitir pasos (desplazamientos) constantes arbitrarios.
- Las arquitecturas modernas también cuentan con los modos de direccionamiento de dispersión/recolección (también llamados indexados ).
- Las arquitecturas avanzadas también pueden incluir soporte para la carga y el almacenamiento de segmentos . La carga de segmentos lee un vector de la memoria, donde cada elemento es una estructura de datos que contiene múltiples miembros. Los miembros se extraen de la estructura de datos (elemento) y cada miembro extraído se coloca en un registro de vector diferente.
- Algunas arquitecturas avanzadas tienen variantes de fallo primero de la carga y almacenamiento vectorial estándar (explicado a continuación).
- El IBM 370 también tenía carga/almacenamiento vectorial disperso que usaba una máscara de bits para especificar, secuencialmente, qué elementos cargar/almacenar: en efecto, el concepto de compresión/expansión pero registro de memoria en lugar de (abajo) registro de registro.
- Operaciones enmascaradas : las máscaras de predicados permiten construcciones if/then/else paralelas sin recurrir a bifurcaciones. Esto permite vectorizar el código con sentencias condicionales.
- Comprimir y expandir : generalmente mediante una máscara de bits, los datos se comprimen o expanden (redistribuyen) linealmente según si los bits de la máscara están activados o desactivados, conservando siempre el orden secuencial y sin duplicar valores (a diferencia de Gather-Scatter, también conocido como permutación). Estas instrucciones se incluyen en AVX-512 .
- Registro Gather, Scatter (también conocido como permute) [ 34 ] : una variación menos restrictiva y más genérica del tema compress/expand que toma un vector para especificar los índices que se usarán para "reordenar" otro vector. Gather/scatter es más complejo de implementar que compress/expand y, al ser inherentemente no secuencial, puede interferir con el encadenamiento de vectores . No debe confundirse con los modos de carga/almacenamiento de memoria Gather-scatter ; las operaciones vectoriales Gather/scatter actúan sobre los registros vectoriales y a menudo se denominan instrucciones permute.
- Splat y Extract , útiles para la interacción entre escalares y vectores, transmiten un único valor a través de un vector o extraen un elemento de un vector, respectivamente.
- Iota : una instrucción muy simple y estratégicamente útil que inserta valores inmediatos que se incrementan secuencialmente en elementos sucesivos. Generalmente comienza desde cero.
- Reducción e Iteración : operaciones que realizan mapreduce en un vector (por ejemplo, encontrar el valor máximo de un vector completo o sumar todos los elementos). La iteración tiene la forma
x[i] = y[i] + x[i-1]donde la reducción tiene la formax = y[0] + y[1]… + y[n-1] - Soporte para multiplicación de matrices : ya sea cargando datos algorítmicamente desde la memoria, reordenando (reasignando) el acceso normalmente lineal a los elementos del vector, o proporcionando "acumuladores", se pueden procesar matrices de tamaño arbitrario de manera eficiente. IBM POWER10 proporciona instrucciones MMA [ 35 ] aunque para anchos de matriz arbitrarios que no se ajustan al tamaño SIMD exacto se necesitan técnicas de repetición de datos que desperdician recursos del archivo de registros. [ 36 ] [ 37 ] NVidia proporciona una API CUDA de matriz de alto nivel aunque los detalles internos no están disponibles. [ 38 ] La técnica más eficiente en recursos es el reordenamiento in situ del acceso a datos vectoriales que de otro modo serían lineales.
- Formatos matemáticos avanzados : a menudo incluyen aritmética de campo de Galois , pero pueden incluir decimal codificado en binario o decimal de punto fijo, y admiten operaciones aritméticas mucho mayores (de precisión arbitraria) mediante el uso de acarreo de entrada y salida paralelo.
- Manipulación de bits , incluyendo versiones vectorizadas de operaciones de permutación a nivel de bits, inserción y extracción de campos de bits, operaciones de centrifugación, conteo de población y muchas otras .
Características de procesamiento vectorial de la GPU
Dado que muchas aplicaciones de sombreado 3D necesitan operaciones trigonométricas , así como vectores cortos para operaciones comunes (RGB, ARGB, XYZ, XYZW), las GPU modernas suelen admitir lo siguiente, además de lo que se encuentra en los procesadores vectoriales:
- Subvectores : los elementos suelen contener dos, tres o cuatro subelementos (vec2, vec3, vec4) donde cualquier bit de una máscara de predicado se aplica a todo el vec2/3/4, no a los elementos del subvector. Los subvectores también se introducen en RISC-V RVV (denominado "LMUL"). [ 39 ] Los subvectores son una parte integral crítica de la especificación Vulkan SPIR-V .
- El intercambio de subvectores , también conocido como "reorganización de carriles", permite realizar cálculos entre elementos de subvectores sin necesidad de instrucciones adicionales (costosas e ineficientes) para mover los subelementos a los "carriles" SIMD correctos, y además ahorra bits de máscara de predicado. En la práctica, se trata de una mini-permutación en vuelo del subvector, una característica muy presente en los binarios de sombreadores 3D (hasta un 20 % de todas las instrucciones [ 40 ] ) y tan importante que forma parte de la especificación Vulkan SPIR-V. El Broadcom Videocore IV utiliza la terminología "rotación de carriles" [ 41 ], mientras que el resto de la industria utiliza el término "intercambio" [ 42 ] .
- Las operaciones trascendentales —como el seno , el coseno y el logaritmo— son mucho más frecuentes en 3D que en muchas cargas de trabajo de computación de alto rendimiento (HPC) exigentes . Sin embargo, resulta interesante que la velocidad sea mucho más importante que la precisión en 3D para las GPU, donde el cálculo de coordenadas de píxeles no requiere alta precisión. La especificación Vulkan reconoce esto y establece requisitos de precisión sorprendentemente bajos, lo que permite que el hardware de la GPU reduzca el consumo de energía. El concepto de reducir la precisión cuando no es necesaria se explora en la extensión MIPS-3D .
Fallo (o avería) primero
Introducido en ARM SVE2 y RISC-V, RVV es el concepto de cargas vectoriales secuenciales especulativas. ARM SVE2 tiene un registro especial llamado "Registro de Primera Falla" [ 43 ] , donde RVV modifica (trunca) la longitud del vector (VL) [ 44 ] .
El principio básico de ffirst consiste en intentar una carga vectorial secuencial de gran tamaño, pero permitiendo que el hardware trunque arbitrariamente la cantidad real cargada a la cantidad que se completaría sin generar un fallo de memoria, o simplemente a una cantidad (mayor que cero) que resulte más conveniente. Lo importante es que las instrucciones subsiguientes reciben una notificación o pueden determinar con exactitud cuántas cargas se realizaron correctamente, utilizando dicha cantidad para trabajar únicamente con los datos que se han cargado realmente.
Contrastemos esta situación con SIMD, que tiene un ancho de carga fijo (inflexible) y un ancho de procesamiento de datos fijo, incapaz de manejar cargas que cruzan los límites de la página, e incluso si pudieran, no podrían adaptarse a lo que realmente tuvo éxito. Sin embargo, paradójicamente, si el programa SIMD intentara averiguar de antemano (en cada bucle interno, cada vez) qué podría tener éxito de manera óptima, esas instrucciones solo servirían para obstaculizar el rendimiento porque, por necesidad, formarían parte del bucle interno crítico.
Esto empieza a insinuar la razón por la que ffirst es tan innovador, y se ilustra mejor con memcpy o strcpy cuando se implementan con SIMD estándar de 128 bits sin predicado y sin ffirst . Para IBM POWER9, el número de instrucciones optimizadas manualmente para implementar strncpy supera las 240. [ 45 ] Por el contrario, la misma rutina strncpy en ensamblador RVV optimizado manualmente consta de tan solo 22 instrucciones. [ 46 ]
El ejemplo SIMD anterior podría potencialmente fallar al final de la memoria, debido a intentos de leer demasiados valores; también podría causar una cantidad significativa de fallos de página o desalineados al cruzar límites de manera similar. En cambio, al permitir que la arquitectura vectorial decida cuántos elementos cargar, la primera parte de una instrucción strncpy, si comienza inicialmente en un límite de memoria subóptimo, puede devolver suficientes cargas como para que en iteraciones posteriores del bucle los lotes de lecturas de memoria vectorizadas se alineen de manera óptima con las cachés subyacentes y las configuraciones de memoria virtual. Además, el hardware puede aprovechar la oportunidad para finalizar las lecturas de memoria de cualquier iteración del bucle exactamente en un límite de página (evitando una costosa segunda búsqueda en la TLB), con la ejecución especulativa preparando la siguiente página de memoria virtual mientras los datos aún se procesan en el bucle actual. Todo esto lo determina el hardware, no el programa en sí. [ 47 ]
Rendimiento y velocidad
Sea r la relación de velocidad vectorial y f la relación de vectorización. Si el tiempo que tarda la unidad vectorial en sumar un array de 64 números es 10 veces más rápido que su equivalente escalar, entonces 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 deduce la aceleración alcanzable de:
Por lo tanto, incluso si el rendimiento de la unidad vectorial es muy alto () hay una aceleración menor quelo que sugiere que la relación f es crucial para el rendimiento. Esta relación depende de la eficiencia de la compilación, como la adyacencia de los elementos en la memoria.
Véase también
- Vectorización automática
- Procesador de barril
- Encadenamiento (procesamiento vectorial)
- Matriz reconfigurable de grano grueso
- Núcleo de cómputo
- Computadora para operaciones con funciones
- La taxonomía de Duncan sobre procesadores vectoriales segmentados
- GPGPU
- Historia de la supercomputación
- RISC-V , un estándar ISA abierto con una extensión vectorial de ancho variable asociada .
- Procesamiento de flujos
- Arquitectura de supercomputadoras
- Arquitectura SX
- Unidad de procesamiento tensorial
Referencias
- ↑ "asm-lessons/lesson_01/index.md en main · FFmpeg/asm-lessons" . GitHub . Consultado el 25 de abril de 2025 .
- ↑ Parkinson, Dennis (17 de junio de 1976). "Computadoras por miles" . New Scientist . págs. 626–627 . Recuperado el 7 de julio de 2024 .
- ↑ BN Malinovsky (1995). La historia de la tecnología informática en sus rostros (en ruso) . KIT. ISBN 5770761318.
- ↑ Grupo de Investigación Vertical MIAOW
- ↑ MIAOW GPU
- 1 2 "Andes anuncia el procesador vectorial multinúcleo RISC-V de 1024 bits: AX45MPV" (Comunicado de prensa). GlobeNewswire. 7 de diciembre de 2022. Consultado el 23 de diciembre de 2022 .
- ↑ Miyaoka, Y.; Choi, J.; Togawa, N.; Yanagisawa, M.; Ohtsuki, T. (2002). Un algoritmo de generación de unidades de hardware para la síntesis de núcleos de procesador con instrucciones SIMD empaquetadas . Conferencia Asia-Pacífico sobre Circuitos y Sistemas. Vol. 1. pp. 171–176 . doi : 10.1109/APCCAS.2002.1114930 . hdl : 2065/10689 .
- ↑
- Manual de instrucciones de la familia FR400 (PDF) . Fujitsu. 1 de enero de 2004.
- ↑ David Patterson ; Andrew Waterman (18 de septiembre de 2017). "Las instrucciones SIMD se consideran perjudiciales" . Computer Architecture Today .
A diferencia de SIMD, tiene un registro de longitud de vector vl, que hace que las instrucciones vectoriales funcionen con cualquier valor de n.
- ↑ Manual de referencia de hardware de la serie Cray-1 S (PDF) . Cray Research. Noviembre de 1981. págs. 6-12 .
- ^ "Riscv-v-spec/V-spec.adoc en master · riscv/Riscv-v-spec" . GitHub . 16 de junio de 2023.
- ↑ "Manual de referencia del lenguaje ensamblador de Vector Engine" (PDF) . 16 de junio de 2023.
- ↑ IBM System/370 Vector Operations (PDF) (Tercera edición). IBM Corporation. Agosto de 1986. SA22-7125-2 . Consultado el 20 de septiembre de 2018 .
- ↑ Manual de referencia de hardware del sistema informático CDC Cyber 200 Modelo 205 (PDF) . Control Data Corporation. 11 de noviembre de 1983.
- ↑ "Versión de la extensión Vector 1.0, congelada para revisión pública · riscvarchive/Riscv-v-spec" . GitHub .
- ↑ "Documentación – Desarrollador Arm" .
- ↑ "Conocimientos y referencias" . Taylor & Francis . 3 de mayo de 2015. Consultado el 19 de febrero de 2026 .
- ↑ Patterson, David A.; Hennessy , John L. (1998). Organización y diseño de computadoras: la interfaz hardware/software, páginas 751-752 (2.ª ed.). Morgan Kaufmann. págs. 751-752 . ISBN 155860491X.
- ^ "Riscv-v-spec/V-spec.adoc en master · riscv/Riscv-v-spec" . GitHub . 19 de noviembre de 2022.
- ↑ Manual del programador de Videocore IV
- ↑ Análisis de la QPU de Videocore IV por Jeff Bush
- ↑ "Programación para neón - Parte 3 Multiplicación de matrices" . 11 de septiembre de 2013.
- ↑ SIMD considerado perjudicial
- ↑ Tutorial de ARM SVE2
- ↑ IBM System/370 Vector Operations (PDF) (Tercera edición). IBM Corporation. Agosto de 1986. SA22-7125-2 . Consultado el 20 de septiembre de 2018 .
- ↑ "Ejemplo de Daxpy" .
- ↑ "Se consideran perjudiciales las instrucciones de SIMD" . 18 de septiembre de 2017.
- ↑ "Sse - Difusión de 1 a 4 y reducción de 4 a 1 en AVX-512" .
- ↑ "Ensamblaje: la forma más rápida de realizar una suma vectorial SSE horizontal (u otra reducción)" .
- ^ "Riscv-v-spec/V-spec.adoc en master · riscv/Riscv-v-spec" . GitHub . 19 de noviembre de 2022.
- ↑ Resumen de Cray
- ↑ RISC-V RVV ISA
- ↑ Descripción general del SX-Arora
- ↑ Instrucciones de recolección y dispersión del registro RVV
- ↑ "Procesador POWER10 de IBM - William Starke y Brian W. Thompto, IBM" . YouTube . 25 de septiembre de 2020. Archivado del original el 11 de diciembre de 2021.
- ↑ 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 ].
- ↑ Krikelis, Anargyros (1996). «Un procesador modular masivamente paralelo para el procesamiento de visualización volumétrica» . Computación de alto rendimiento para gráficos y visualización por computadora . págs. 101–124 . doi : 10.1007/978-1-4471-1011-8_8 . ISBN 978-3-540-76016-0.
- ↑ "Guía de programación CUDA C++" .
- ↑ LMUL > 1 en RVV
- ↑ "Tom Forsyth: el ciclo de vida de un conjunto de instrucciones" . 22 de agosto de 2020.
- ↑ Patente estadounidense abandonada US20110227920-0096
- ↑ Videocore IV QPU
- ↑ Introducción a ARM SVE2
- ↑ Cargas RVV con prioridad de fallo
- ↑ PARCHE a libc6 para agregar strncpy optimizado para POWER9
- ↑ Ejemplo de RVV strncpy
- ↑ Artículo sobre ARM SVE2 de N. Stevens
- unidad central de procesamiento
- Coprocesadores
- Computación paralela
- supercomputadoras vectoriales