En informática , un procesador vectorial o un procesador de matriz es una unidad central de procesamiento (CPU) que implementa un conjunto de instrucciones que contiene instrucciones que operan en matrices unidimensionales de datos llamadas vectores , en comparación con los procesadores escalares , cuyas instrucciones operan en elementos de datos individuales. Los procesadores vectoriales pueden mejorar enormemente el rendimiento en determinadas cargas de trabajo, en particular la simulación numérica y tareas similares. 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 diversasPlataformas Cray . La rápida caída en la relación precio-rendimiento de los diseños de microprocesadores convencionales llevó a la desaparición de la supercomputadora vectorial a fines de la década de 1990.
A partir de 2016, la [actualizar]mayoría de las CPU básicas implementan arquitecturas que cuentan con instrucciones SIMD para una forma de procesamiento vectorial en múltiples conjuntos de datos (vectorizados). Los ejemplos comunes incluyen las instrucciones MMX , SSE y AVX de Intel x86 , 3DNow! De AMD . extensiones, de Sparc VIS extensión, PowerPC 's AltiVec y MIPS' MSA . Las técnicas de procesamiento de vectores también operan en el hardware de las consolas de videojuegos y en los aceleradores de gráficos . En 2000, IBM , Toshiba y Sony colaboraron para crear el procesador Cell .
Otros diseños de CPU incluyen algunas instrucciones múltiples para el procesamiento de vector en varios conjuntos de datos (vectorizados), típicamente conocidos como MIMD ( M ultiple I NSTRUCCIÓN, M ultiple D ata) y realizadas con VLIW ( V ery L ong I NSTRUCCIÓN W ord). El procesador vectorial Fujitsu FR-V VLIW combina ambas tecnologías.
Historia
Trabajo temprano
El desarrollo del procesamiento de vectores comenzó a principios de la década de 1960 en Westinghouse 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 matemáticos simples bajo el control de una sola CPU maestra . La CPU alimentó 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 solo algoritmo a un gran conjunto de datos , alimentado en forma de matriz.
En 1962, Westinghouse canceló el proyecto, pero el esfuerzo se reinició en la Universidad de Illinois 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, tenía solo 64 ALU y podía alcanzar solo de 100 a 150 MFLOPS. Sin embargo, demostró que el concepto básico era sólido y, cuando se usaba en aplicaciones con uso intensivo de datos, como la dinámica de fluidos computacional , ILLIAC era la máquina más rápida del mundo. El enfoque ILLIAC de usar ALU separadas para cada elemento de datos no es común a diseños posteriores y, a menudo, se lo denomina en una categoría separada, computación masivamente paralela .
Kartsev presentó y desarrolló una computadora para operaciones con funciones en 1967. [1]
Supercomputadoras
La primera implementación exitosa del procesamiento de vectores ocurrió en 1966, cuando se introdujeron tanto Control Data Corporation STAR-100 como Texas Instruments Advanced Scientific Computer (ASC).
La ALU ASC básica (es decir, "una tubería") utilizaba una arquitectura de tubería que admitía cálculos tanto escalares como vectoriales, con un rendimiento máximo que alcanzaba aproximadamente 20 MFLOPS, que se lograba 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 expandidos.
Por lo demás, el STAR era más lento que las propias supercomputadoras de los CDC , como el CDC 7600 , pero en las tareas relacionadas con los datos podían mantenerse al día siendo mucho más pequeño y menos costoso. 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 que realmente acelerara algo.
La técnica del vector fue explotada por primera vez en 1976 por el famoso Cray-1 . En lugar de dejar los datos en la memoria como STAR y 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 STAR 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 los registros y luego aplicaría tantas operaciones como pudiera a esos datos, por lo tanto evitando muchas de las operaciones de acceso a la memoria mucho más lentas.
El diseño de Cray utilizó el paralelismo de la tubería 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ó que un lote de instrucciones vectoriales se canalizara a cada una de las subunidades de ALU, una técnica que llamaron encadenamiento de vectores . El Cray-1 normalmente tenía un rendimiento de aproximadamente 80 MFLOPS, pero con hasta tres cadenas en funcionamiento, podía alcanzar un máximo de 240 MFLOPS y promediar alrededor de 150, mucho más rápido que cualquier máquina de la época.
Siguieron otros ejemplos. Control Data Corporation intentó volver a ingresar al mercado de alta gama nuevamente con su máquina ETA-10 , pero se vendió mal y tomaron eso como una oportunidad para dejar el campo de la supercomputación por completo. 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 por lo general eran un poco más rápidas y mucho más pequeñas. Sistemas de punto flotante con base en Oregón (FPS) construyó procesadores de matriz complementarios para miniordenadores , y luego construyó sus propias minisupercomputadoras .
En todo momento, Cray continuó siendo el líder en rendimiento, superando continuamente a la competencia con una serie de máquinas que llevaron al Cray-2 , Cray X-MP y Cray Y-MP . Desde entonces, el mercado de las supercomputadoras se ha centrado mucho más en el procesamiento masivamente paralelo que en mejores implementaciones de procesadores vectoriales. Sin embargo, reconociendo los beneficios del procesamiento de vectores, IBM desarrolló la Arquitectura de Vector Virtual para su uso en supercomputadoras que acoplan varios procesadores escalares para que actúen como un procesador de vectores.
Aunque las supercomputadoras vectoriales que se asemejan al Cray-1 son menos populares en estos días, NEC ha continuado fabricando este tipo de computadora 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 se asemeja físicamente a un coprocesador gráfico, pero en lugar de servir como coprocesador, es la computadora principal con el ordenador compatible con PC en el que está conectado para funciones de soporte.
GPU
Las unidades de procesamiento de gráficos ( GPU ) modernas incluyen una serie de canales de sombreado que pueden ser impulsados por núcleos de cómputo , que pueden considerarse procesadores vectoriales (utilizando una estrategia similar para ocultar latencias de memoria).
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 básicamente dice "agregue A a B y ponga 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 sin procesar y, en cambio, se "apuntan" al pasar 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 se queda 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 a través de varias subunidades sucesivamente. 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" es 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 montaje , de modo que el decodificador de direcciones esté en uso constante. 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 mucho más rápido y de manera más eficiente que si lo hiciera una a la vez.
Los procesadores vectoriales llevan este concepto un paso más allá. En lugar de canalizar solo las instrucciones, también canalizan los datos en sí. El procesador recibe instrucciones que dicen no solo agregar A a B, sino agregar todos los números "de aquí para aquí" a todos los números "de allí para 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 está simplemente implícito en la definición de la instrucción en sí misma que la instrucción operará nuevamente en 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 simple tarea de sumar dos grupos de 10 números. En un lenguaje de programación normal, uno escribiría un "bucle" que recogiera cada uno de los pares de números por turno y luego los sumara. Para la CPU, esto se vería así:
; Máquina RISC hipotética ; sume 10 números en a a 10 números en b, almacenando los resultados en c ; suponga que a, byc son ubicaciones de memoria en sus respectivos registros mover $ 10 , contar ; cuenta: = 10 bucle: carga r1 , una carga r2 , b suma r3 , r1 , r2 ; r3: = r1 + r2 almacenar r3 , c agregar a , a , $ 4 ; seguir adelante sumar b , b , $ 4 sumar c , c , $ 4 recuento de decimales ; decrementar el recuento de jnez , bucle ; bucle hacia atrás si el recuento aún no es 0 ret
Pero para un procesador de vectores, esta tarea se ve considerablemente diferente:
; suponga que tenemos registros vectoriales v1-v3 con un tamaño mayor que 10 mover $ 10 , contar ; count = 10 vload v1 , a , count vload v2 , b , count vadd v3 , v1 , v2 vstore v3 , c , count ret
Hay varios ahorros inherentes a este enfoque. Por un lado, solo se necesitan tres traducciones de direcciones. Dependiendo de la arquitectura, esto puede representar un ahorro significativo en sí mismo. Otro ahorro es buscar y decodificar la instrucción en sí, que debe realizarse solo 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.
Pero más que eso, un procesador vectorial puede tener múltiples unidades funcionales agregando esos números en paralelo. La verificación de las dependencias entre esos números no es necesaria ya que una instrucción vectorial especifica múltiples operaciones independientes. Esto simplifica la lógica de control requerida y puede mejorar el rendimiento al evitar paradas. Por tanto, 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 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.
De hecho, los procesadores vectoriales funcionan mejor solo cuando hay grandes cantidades de datos en los que trabajar. Por esta razón, este tipo de CPU se encontraban principalmente en supercomputadoras , ya que las supercomputadoras mismas 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.
Instrucciones vectoriales
El ejemplo de pseudocódigo de vector anterior viene con una gran suposición de que la computadora de vector puede procesar más de diez números en un lote. Para un mayor número de números, no es factible que la computadora tenga un registro tan grande. Como resultado, el procesador vectorial obtiene la capacidad de realizar bucles por sí mismo o expone al programador algún tipo de registro vectorial.
Las instrucciones que se repiten automáticamente se encuentran en las primeras computadoras vectoriales como STAR, 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 REP
prefijo. Sin embargo, solo se pueden realizar cálculos muy simples de manera efectiva en hardware sin un aumento de costos muy grande. Dado que todos los operandos deben estar en la memoria, la latencia causada por el acceso también se vuelve enorme.
El Cray-1 introdujo la idea de utilizar registros de procesador para almacenar datos vectoriales en lotes. De esta manera, se puede hacer mucho más trabajo en cada lote, a costa de requerir que el programador cargue / almacene manualmente datos desde / hacia la memoria para cada lote. Las computadoras SIMD modernas mejoran en Cray al usar directamente múltiples ALU, para un mayor grado de paralelismo en comparación con solo usar la tubería escalar normal. Las máscaras se pueden utilizar para cargar o almacenar de forma selectiva ubicaciones de memoria para una versión de lógica paralela.
Las GPU, que tienen muchas unidades de cálculo pequeñas, utilizan una variante de SIMD llamada Single Instruction Multiple Threads (SIMT). Esto es similar al SIMD moderno, con la excepción de que los "registros vectoriales" son muy anchos y las tuberías tienden a ser largas. La parte de "subprocesamiento" afecta la forma en que se intercambian los datos entre las unidades de cálculo. Además, las GPU y otros procesadores vectoriales externos como el NEC SX-Aurora TSUBASA pueden usar 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 hacer un bucle canalizado sobre 16 unidades para un enfoque híbrido.
La diferencia entre un procesador vectorial tradicional y uno SIMD moderno se puede ilustrar con esta variante de la función "DAXPY":
void iaxpy ( size_t n , int una , const int x [], int y []) { para ( size_t i = 0 ; i < n ; i ++ ) y [ i ] = a * x [ i ] + y [ i ]; }
El código tipo STAR sigue siendo conciso, pero ahora necesitamos 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.
; Suponga que tmp es vmul tmp , a , x , n preasignado ; tmp [i] = a * x [i] vadd y , y , tmp , n ; y [i] = y [i] + tmp [i] ret
Esta moderna máquina SIMD puede realizar la mayor parte de la operación en lotes. El código es mayormente similar a la versión escalar. Suponemos que tanto x como y están correctamente alineados aquí 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. El tiempo necesario sería básicamente el mismo que el de una implementación vectorial de la c = a + b
descrita 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 ; a: = a + 16 adicionales y , y , $ 16 subl n , n , $ 4 ; n: = n - 4 jgz n , vloop ; bucle hacia atrás si n> 0 out: ret
Rendimiento y aceleración
Sea r la relación de velocidad del vector yf la relación de vectorización. Si el tiempo que tarda la unidad vectorial en agregar 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. Sigue la aceleración alcanzable de:
Entonces, incluso si el rendimiento de la unidad vectorial es muy alto () obtenemos una aceleración menor que , lo que sugiere que la razón f es crucial para el desempeño. Esta relación depende de la eficiencia de la compilación como adyacencia de los elementos en la memoria.
Programación de arquitecturas informáticas heterogéneas
Se diseñaron varias máquinas para incluir tanto procesadores tradicionales como procesadores vectoriales, como Fujitsu AP1000 y AP3000. La programación de máquinas tan heterogéneas puede resultar difícil, ya que el desarrollo de programas que aprovechan al máximo las características de los diferentes procesadores aumenta la carga del programador. Aumenta la complejidad del código y disminuye la portabilidad del código al requerir que el código específico del hardware se intercale en todo el código de la aplicación. [2] Equilibrar la carga de trabajo de la aplicación en los procesadores puede ser problemático, especialmente dado que suelen tener diferentes características de rendimiento. Existen diferentes modelos conceptuales para abordar el problema, por ejemplo, utilizando un lenguaje de coordinación y bloques de construcción de programas (bibliotecas de programación o funciones de orden superior). Cada bloque puede tener una implementación nativa diferente para cada tipo de procesador. Los usuarios simplemente programan utilizando estas abstracciones y un compilador inteligente elige la mejor implementación en función del contexto. [3]
Ver también
- Arquitectura SX
- GPGPU
- Computar kernel
- Procesamiento de flujo
- SIMD
- Vectorización automática
- Encadenamiento (procesamiento de vectores)
- Computadora para operaciones con funciones
- RISC-V , un estándar ISA abierto con una extensión de vector de ancho variable asociada.
- Procesador de barril
- Unidad de procesamiento de tensor
Referencias
- ^ Malinovsky, BN (1995). La historia de la tecnología informática en sus caras (en ruso) . Kiew: "KIT" firme. ISBN 5-7707-6131-8.
- ^ Kunzman, DM; Kale, LV (2011). "Programación de sistemas heterogéneos". 2011 IEEE International Symposium on Parallel and Distributed Processing Workshops and Phd Forum . pag. 2061. doi : 10.1109 / IPDPS.2011.377 . ISBN 978-1-61284-425-1.
- ^ John Darlinton; Moustafa Ghanem; Yike Guo; Hing Wing To (1996), "Organización guiada de recursos en computación paralela heterogénea", Journal of High Performance Computing , 4 (1): 13-23, CiteSeerX 10.1.1.37.4309
enlaces externos
- La historia del desarrollo de la computación paralela (de 1955 a 1993)