Articulo de referencia

Vectorización automática

La vectorización automática , en computación paralela , es un caso especial de paralelización automática , donde un programa informático se convierte de una implementación escal...

La vectorización automática , en computación paralela , es un caso especial de paralelización automática , donde un programa informático se convierte de una implementación escalar , que procesa un único par de operandos a la vez, a una implementación vectorial , que procesa una operación en múltiples pares de operandos simultáneamente. Por ejemplo, las computadoras convencionales modernas, incluidas las supercomputadoras especializadas , suelen tener operaciones vectoriales que realizan simultáneamente operaciones como las siguientes cuatro sumas (mediante hardware SIMD o SPMD ):

do1=a1+b1do2=a2+b2do3=a3+b3do4=a4+b4{\displaystyle {\begin{aligned}c_{1}&=a_{1}+b_{1}\\c_{2}&=a_{2}+b_{2}\\c_{3}&=a_{3}+b_{3}\\c_{4}&=a_{4}+b_{4}\end{aligned}}}

Sin embargo, en la mayoría de los lenguajes de programación, normalmente se escriben bucles que realizan sumas de muchos números de forma secuencial. Aquí hay un ejemplo de dicho bucle, escrito en C :

para ( i = 0 ; i < n ; i ++ ) c [ i ] = a [ i ] + b [ i ];

Un compilador vectorizador transforma dichos bucles en secuencias de operaciones vectoriales. Estas operaciones vectoriales realizan sumas en bloques de elementos de los arreglos a, by c. La vectorización automática es un tema de investigación importante en la informática.

Fondo

Las primeras computadoras solían tener una unidad lógica, que ejecutaba una instrucción sobre un par de operandos a la vez. Por lo tanto, los lenguajes y programas informáticos se diseñaban para ejecutarse secuencialmente. Sin embargo, las computadoras modernas pueden realizar muchas tareas simultáneamente. Por ello, muchos compiladores optimizadores realizan vectorización automática, transformando partes de programas secuenciales en operaciones paralelas.

La vectorización de bucles transforma los bucles procedimentales asignando una unidad de procesamiento a cada par de operandos. Los programas pasan la mayor parte del tiempo dentro de estos bucles. Por lo tanto, la vectorización puede acelerarlos significativamente, especialmente con grandes conjuntos de datos. La vectorización de bucles está implementada en los conjuntos de instrucciones MMX , SSE y AVX de Intel , en AltiVec de Power ISA , en NEON , SVE y SVE2 de ARM , y en los conjuntos de instrucciones Vector Extension de RISC-V .

Numerosas limitaciones impiden o dificultan la vectorización. En ocasiones, la vectorización puede ralentizar la ejecución, por ejemplo, debido a la sincronización de la tubería o a la sincronización del movimiento de datos. El análisis de dependencia de bucles identifica aquellos que pueden vectorizarse, basándose en la dependencia de datos de las instrucciones dentro de los bucles.

Garantías

La vectorización automática, al igual que cualquier optimización de bucles u otra optimización en tiempo de compilación, debe preservar con exactitud el comportamiento del programa.

dependencias de datos

Durante la ejecución, deben respetarse todas las dependencias para evitar resultados incorrectos.

En general, las dependencias invariantes de bucle y las dependencias léxicas hacia adelante se pueden vectorizar fácilmente, y las dependencias léxicas hacia atrás se pueden transformar en dependencias léxicas hacia adelante. Sin embargo, estas transformaciones deben realizarse de forma segura para garantizar que la dependencia entre todas las instrucciones se mantenga igual que la original.

Las dependencias cíclicas deben procesarse independientemente de las instrucciones vectorizadas.

En ocasiones, el compilador puede asumir una dependencia con excesiva cautela. Algunos compiladores ofrecen una directiva llamada ivdep para indicarles que ignoren las dependencias. Aplicar incorrectamente dicha directiva provocaría resultados erróneos. [ 1 ]

Precisión de los datos

La precisión de los enteros (tamaño de bits) debe mantenerse durante la ejecución de las instrucciones vectoriales. La instrucción vectorial correcta debe seleccionarse en función del tamaño y el comportamiento de los enteros internos. Asimismo, con tipos de enteros mixtos, se debe tener especial cuidado al promoverlos o degradarlos correctamente sin perder precisión. Se debe prestar especial atención a la extensión de signo (ya que varios enteros se almacenan en el mismo registro) y durante las operaciones de desplazamiento, o las operaciones con bits de acarreo que, de otro modo, se tendrían en cuenta.

Punto flotante

Por defecto, se conserva la semántica de punto flotante de IEEE-754 , lo que a menudo impide por completo la vectorización. Consideremos el siguiente bucle trivial que calcula una suma sobre un array:

float suma ( float * A , int n ) { float suma = 0 ; for ( int i = 0 ; i < n ; ++ i ) suma += A [ i ]; return suma ; }

Debido a que las operaciones de punto flotante no son asociativas (cambiar el orden de la suma cambia el resultado), no se puede permitir que un compilador vectorice el bucle. Solo cuando se le permite explícitamente al compilador reasociar (reordenar las operaciones como si fueran asociativas), puede compilar a algo parecido a: [ 2 ] : § Reducciones

float sum ( float * A , int n ) { float4 sum4 = { 0 }, * A4 = A ; int n4 = n / 4 ; for ( int i = 0 ; i < n4 ; ++ i ) { sum4 += A4 [ i ]; }float suma = suma4 [ 0 ] + suma4 [ 1 ] + suma4 [ 2 ] + suma4 [ 3 ]; para ( int i = n / 4 ; i < n ; ++ i ) { suma += A [ i ]; }devolver suma ; }

En este caso, los resultados variarán ligeramente con respecto al original, pero el error de redondeo asintótico es similar. Este es un ejemplo de reducción .

Las grandes variaciones, incluso sin tener en cuenta la norma IEEE-754, suelen indicar un error de programación. Una causa común es que la reasociación completa puede invalidar los algoritmos de suma compensada . Comandos más restringidos, como la cláusula de reducción de OpenMP, permiten una configuración más precisa.

Teoría

Para vectorizar un programa, el optimizador del compilador debe primero comprender las dependencias entre las instrucciones y realinearlas, si es necesario. Una vez que se han identificado las dependencias, el optimizador debe organizar adecuadamente las instrucciones de implementación, transformando las instrucciones candidatas apropiadas en instrucciones vectoriales, que operan sobre múltiples elementos de datos.

Construyendo el grafo de dependencias

El primer paso consiste en construir el grafo de dependencias , identificando qué instrucciones dependen de otras. Esto implica examinar cada instrucción e identificar cada elemento de datos al que accede, asignar los modificadores de acceso a matrices a funciones y comprobar la dependencia de cada acceso con todos los demás en todas las instrucciones. El análisis de alias puede utilizarse para certificar que las diferentes variables acceden (o se intersecan) en la misma región de la memoria.

El grafo de dependencias contiene todas las dependencias locales cuya distancia no supera el tamaño del vector. Por lo tanto, si el registro vectorial es de 128 bits y el tipo de matriz es de 32 bits, el tamaño del vector es 128/32 = 4. Las demás dependencias no cíclicas no invalidan la vectorización, ya que no habrá accesos concurrentes en la misma instrucción vectorial.

Supongamos que el tamaño del vector es el mismo que 4 enteros:

for ( i = 0 ; i < 128 ; i ++ ) { a [ i ] = a [ i -16 ]; // 16 > 4, se puede ignorar sin problema a [ i ] = a [ i -1 ]; // 1 < 4, se mantiene en el gráfico de dependencias }

Agrupamiento

Utilizando el gráfico, el optimizador puede agrupar los componentes fuertemente conectados (SCC) y separar las sentencias vectorizables del resto.

Por ejemplo, consideremos un fragmento de programa que contiene tres grupos de instrucciones dentro de un bucle: (SCC1+SCC2), SCC3 y SCC4, en ese orden, donde solo el segundo grupo (SCC3) puede vectorizarse. El programa final contendrá entonces tres bucles, uno para cada grupo, con solo el del medio vectorizado. El optimizador no puede unir el primero con el último sin violar el orden de ejecución de las instrucciones, lo que invalidaría las garantías necesarias.

Detección de modismos

Algunas dependencias no evidentes pueden optimizarse aún más basándose en modismos específicos.

Por ejemplo, las siguientes auto-dependencias de datos se pueden vectorizar porque el valor de los valores de la derecha ( RHS ) se obtiene y luego se almacena en el valor de la izquierda, por lo que no hay forma de que los datos cambien dentro de la asignación.

a [ i ] = a [ i ] + a [ i + 1 ];

La autodependencia de los escalares puede vectorizarse mediante la eliminación de variables .

Marco general

El marco general para la vectorización de bucles se divide en cuatro etapas:

  • Preludio : Aquí se preparan las variables independientes del bucle para su uso dentro del mismo. Esto normalmente implica moverlas a registros vectoriales con patrones específicos que se utilizarán en las instrucciones vectoriales. Este es también el lugar para insertar la comprobación de dependencia en tiempo de ejecución. Si la comprobación determina que la vectorización no es posible, se salta a la limpieza .
  • Bucle(s) : Todos los bucles vectorizados (o no), separados por clústeres SCC en orden de aparición en el código original.
  • Postludio : Devuelve todas las variables independientes del bucle, inducciones y reducciones.
  • Limpieza : Implementar bucles simples (no vectorizados) para las iteraciones al final de un bucle que no sean un múltiplo del tamaño del vector o cuando las comprobaciones en tiempo de ejecución prohíban el procesamiento del vector.

Tiempo de ejecución frente a tiempo de compilación

Algunas vectorizaciones no se pueden verificar completamente en tiempo de compilación. Por ejemplo, las funciones de biblioteca pueden eludir la optimización si los datos que procesan son proporcionados por quien las llama. Incluso en estos casos, la optimización en tiempo de ejecución aún puede vectorizar los bucles sobre la marcha.

Esta comprobación en tiempo de ejecución se realiza en la fase previa y dirige el flujo a instrucciones vectorizadas si es posible; de ​​lo contrario, vuelve al procesamiento estándar, dependiendo de las variables que se estén pasando en los registros o variables escalares.

El siguiente código se puede vectorizar fácilmente en tiempo de compilación, ya que no depende de parámetros externos. Además, el lenguaje garantiza que ninguna de ellas ocupará la misma región de memoria que ninguna otra variable, puesto que son variables locales y residen únicamente en la pila de ejecución .

int a [ 128 ]; int b [ 128 ]; // inicializar bpara ( i = 0 ; i < 128 ; i ++ ) a [ i ] = b [ i ] + 5 ;

Por otro lado, el código que aparece a continuación no contiene información sobre las posiciones de memoria, ya que las referencias son punteros y la memoria a la que apuntan puede superponerse.

void compute ( int * a , int * b ) { int i ; for ( i = 0 ; i < 128 ; i ++ , a ++ , b ++ ) * a = * b + 5 ; }

Una comprobación rápida en tiempo de ejecución de la dirección de memoria de a y b , junto con el espacio de iteración del bucle (128), es suficiente para determinar si los arreglos se superponen o no, revelando así cualquier dependencia. (Cabe destacar que, a partir de C99, calificar los parámetros con la palabra clave `restrict` (en este caso: int *restrict a, int *restrict b) indica al compilador que los rangos de memoria a los que apuntan a y b no se superponen, lo que produce el mismo resultado que en el ejemplo anterior).

Existen algunas herramientas para analizar dinámicamente las aplicaciones existentes y evaluar el potencial latente inherente para el paralelismo SIMD, explotable a través de avances adicionales del compilador y/o mediante cambios manuales en el código. [ 3 ]

Técnicas

Un ejemplo sería un programa para multiplicar dos vectores de datos numéricos. Un enfoque escalar sería algo así como:

para ( i = 0 ; i < 1024 ; i ++ ) c [ i ] = a [ i ] * b [ i ];

Esto podría vectorizarse para que se viera algo así:

para ( i = 0 ; i < 1024 ; i += 4 ) c [ i : i + 3 ] = a [ i : i + 3 ] * b [ i : i + 3 ];

Aquí, c[i:i+3] representa los cuatro elementos del arreglo desde c[i] hasta c[i+3], y el procesador vectorial puede realizar cuatro operaciones con una sola instrucción vectorial. Dado que las cuatro operaciones vectoriales se completan en aproximadamente el mismo tiempo que una instrucción escalar, el enfoque vectorial puede ser hasta cuatro veces más rápido que el código original.

Existen dos enfoques de compilación distintos: uno basado en la técnica de vectorización convencional y otro basado en el desenrollado de bucles .

Vectorización automática a nivel de bucle

Esta técnica, utilizada para máquinas vectoriales convencionales, intenta encontrar y explotar el paralelismo SIMD a nivel de bucle. Consta de dos pasos principales, como se describe a continuación.

  1. Encuentra un bucle interno que pueda ser vectorizado.
  2. Transforma el bucle y genera códigos vectoriales.

En el primer paso, el compilador busca obstáculos que puedan impedir la vectorización. Un obstáculo importante es la dependencia de datos real, que es menor que la longitud del vector. Otros obstáculos incluyen las llamadas a funciones y un número reducido de iteraciones.

Una vez que se determina que el bucle es vectorizable, se divide en segmentos según la longitud del vector y cada instrucción escalar dentro del cuerpo del bucle se reemplaza por la instrucción vectorial correspondiente. A continuación, se muestran las transformaciones de componentes para este paso utilizando el ejemplo anterior.

  • Después de la minería a cielo abierto
para ( i = 0 ; i < 1024 ; i += 4 ) para ( j = 0 ; j < 4 ; j ++ ) c [ i + j ] = a [ i + j ] * b [ i + j ];
  • Después de la distribución del bucle usando arreglos temporales
para ( i = 0 ; i < 1024 ; i += 4 ) { para ( j = 0 ; j < 4 ; j ++ ) tA [ j ] = A [ i + j ]; para ( j = 0 ; j < 4 ; j ++ ) tB [ j ] = B [ i + j ]; para ( j = 0 ; j < 4 ; j ++ ) tC [ j ] = tA [ j ] * tB [ j ]; para ( j = 0 ; j < 4 ; j ++ ) C [ i + j ] = tC [ j ]; }
  • Después de reemplazar con códigos vectoriales
for ( i = 0 ; i < 1024 ; i += 4 ) { vA = vec_ld ( & A [ i ]); vB = vec_ld ( & B [ i ]); vC = vec_mul ( vA , vB ); vec_st ( vC , & C [ i ]); }

Vectorización automática a nivel de bloque básico

Esta técnica relativamente nueva se dirige específicamente a las arquitecturas SIMD modernas con longitudes de vector cortas. [ 4 ] Aunque los bucles se pueden desenrollar para aumentar la cantidad de paralelismo SIMD en los bloques básicos, esta técnica explota el paralelismo SIMD dentro de los bloques básicos en lugar de los bucles. Los dos pasos principales son los siguientes.

  1. El bucle más interno se desenrolla por un factor igual a la longitud del vector para formar un cuerpo de bucle grande.
  2. Las instrucciones escalares isomórficas (que realizan la misma operación) se empaquetan en una instrucción vectorial si las dependencias no lo impiden.

Para mostrar las transformaciones paso a paso de este método, se utiliza de nuevo el mismo ejemplo.

  • Después de desenrollar el bucle (por la longitud del vector, que en este caso se supone que es 4)
for ( i = 0 ; i < 1024 ; i += 4 ) { sA0 = ld ( & A [ i + 0 ]); sB0 = ld ( & B [ i + 0 ]); sC0 = sA0 * sB0 ; st ( sC0 , & C [ i + 0 ]); ... sA3 = ld ( & A [ i + 3 ]); sB3 = ld ( & B [ i + 3 ]); sC3 = sA3 * sB3 ; st ( sC3 , & C [ i + 3 ]); }
  • Después de empacar
for ( i = 0 ; i < 1024 ; i += 4 ) { ( sA0 , sA1 , sA2 , sA3 ) = ld ( & A [ i + 0 : i + 3 ]); ( sB0 , sB1 , sB2 , sB3 ) = ld ( & B [ i + 0 : i + 3 ]); ( sC0 , sC1 , sC2 , sC3 ) = ( sA0 , sA1 , sA2 , sA3 ) * ( sB0 , sB1 , sB2 , sB3 ); st (( sC0 , sC1 , sC2 , sC3 ), & C [ i + 0 : i + 3 ]); }
  • Después de la generación de código
for ( i = 0 ; i < 1024 ; i += 4 ) { vA = vec_ld ( & A [ i ]); vB = vec_ld ( & B [ i ]); vC = vec_mul ( vA , vB ); vec_st ( vC , & C [ i ]); }

Aquí, sA1, sB1, ... representan variables escalares y vA, vB y vC representan variables vectoriales.

La mayoría de los compiladores comerciales de vectorización automática utilizan el enfoque convencional a nivel de bucle, excepto el compilador IBM XL, [ 5 ] que utiliza ambos.

En presencia de flujo de control

La presencia de sentencias if en el cuerpo del bucle requiere la ejecución de instrucciones en todas las rutas de control para combinar los múltiples valores de una variable. Un enfoque general es pasar por una secuencia de transformaciones de código: predicación → vectorización (usando uno de los métodos anteriores) → eliminar predicados vectoriales → eliminar predicados escalares. [ 6 ] Si se utiliza el siguiente código como ejemplo para mostrar estas transformaciones;

para ( i = 0 ; i < 1024 ; i ++ ) si ( A [ i ] > 0 ) C [ i ] = B [ i ]; de lo contrario D [ i ] = D [ i -1 ];
  • Después de la predicción
para ( i = 0 ; i < 1024 ; i ++ ) { P = A [ i ] > 0 ; NP = ! P ; C [ i ] = B [ i ]; ( P ) D [ i ] = D [ i -1 ]; ( NP ) }

donde (P) denota un predicado que protege la afirmación.

  • Después de la vectorización
para ( i = 0 ; i < 1024 ; i += 4 ) { vP = A [ i : i + 3 ] > ( 0 , 0 , 0 , 0 ); vNP = vec_not ( vP ); C [ i : i + 3 ] = B [ i : i + 3 ]; ( vP ) ( NP1 , NP2 , NP3 , NP4 ) = vNP ; D [ i + 3 ] = D [ i + 2 ]; ( NP4 ) D [ i + 2 ] = D [ i + 1 ]; ( NP3 ) D [ i + 1 ] = D [ i ]; ( NP2 ) D [ i ] = D [ i -1 ]; ( NP1 ) }
  • Después de eliminar los predicados vectoriales
para ( i = 0 ; i < 1024 ; i += 4 ) { vP = A [ i : i + 3 ] > ( 0 , 0 , 0 , 0 ); vNP = vec_not ( vP ); C [ i : i + 3 ] = vec_sel ( C [ i : i + 3 ], B [ i : i + 3 ], vP ); ( NP1 , NP2 , NP3 , NP4 ) = vNP ; D [ i + 3 ] = D [ i + 2 ]; ( NP4 ) D [ i + 2 ] = D [ i + 1 ]; ( NP3 ) D [ i + 1 ] = D [ i ]; ( NP2 ) D [ i ] = D [ i -1 ]; ( NP1 ) }
  • Después de eliminar los predicados escalares
for ( i = 0 ; i < 1024 ; i += 4 ) { vP = A [ i : i + 3 ] > ( 0 , 0 , 0 , 0 ); vNP = vec_not ( vP ); C [ i : i + 3 ] = vec_sel ( C [ i : i + 3 ], B [ i : i + 3 ], vP ); ( NP1 , NP2 , NP3 , NP4 ) = vNP ; if ( NP4 ) D [ i + 3 ] = D [ i + 2 ]; if ( NP3 ) D [ i + 2 ] = D [ i + 1 ]; if ( NP2 ) D [ i + 1 ] = D [ i ]; if ( NP1 ) D [ i ] = D [ i -1 ]; }

Reducción de la sobrecarga de vectorización en presencia de flujo de control

La necesidad de ejecutar las instrucciones en todas las rutas de control en el código vectorial ha sido uno de los principales factores que ralentizan el código vectorial con respecto al código escalar. Cuanto más complejo sea el flujo de control y más instrucciones se omitan en el código escalar, mayor será la sobrecarga de vectorización. Para reducir esta sobrecarga, se pueden insertar bifurcaciones vectoriales para omitir instrucciones vectoriales, de forma similar a como las bifurcaciones escalares omiten instrucciones escalares. [ 7 ] A continuación, se utilizan predicados AltiVec para mostrar cómo se puede lograr esto.

  • Línea base escalar (código original)
para ( i = 0 ; i < 1024 ; i ++ ) { si ( A [ i ] > 0 ) { C [ i ] = B [ i ]; si ( B [ i ] < 0 ) D [ i ] = E [ i ]; } }
  • Después de la vectorización en presencia de flujo de control
for ( i = 0 ; i < 1024 ; i += 4 ) { vPA = A [ i : i + 3 ] > ( 0 , 0 , 0 , 0 ); C [ i : i + 3 ] = vec_sel ( C [ i : i + 3 ], B [ i : i + 3 ], vPA ); vT = B [ i : i + 3 ] < ( 0 , 0 , 0 , 0 ); vPB = vec_sel (( 0 , 0 , 0 , 0 ), vT , vPA ); D [ i : i + 3 ] = vec_sel ( D [ i : i + 3 ], E [ i : i + 3 ], vPB ); }
  • Después de insertar ramas vectoriales
para ( i = 0 ; i < 1024 ; i += 4 ) { si ( vec_any_gt ( A [ i : i + 3 ], ( 0 , 0 , 0 , 0 ))) { vPA = A [ i : i + 3 ] > ( 0 , 0 , 0 , 0 ); C [ i : i + 3 ] = vec_sel ( C [ i : i + 3 ], B [ i : i + 3 ], vPA ); vT = B [ i : i + 3 ] < ( 0 , 0 , 0 , 0 ); vPB = vec_sel (( 0 , 0 , 0 , 0 ), vT , vPA ); if ( vec_any_ne ( vPB , ( 0 , 0 , 0 , 0 ))) D [ i : i + 3 ] = vec_sel ( D [ i : i + 3 ], E [ i : i + 3 ], vPB ); } }

En el código final con bifurcaciones vectoriales, cabe destacar dos aspectos: primero, la instrucción que define el predicado para vPA también se incluye dentro del cuerpo de la bifurcación vectorial externa mediante vec_any_gt; segundo, la rentabilidad de la bifurcación vectorial interna para vPB depende de la probabilidad condicional de que vPB tenga valores falsos en todos los campos, dado que vPA tiene valores falsos en todos los campos.

Consideremos un ejemplo donde la rama externa en la implementación escalar siempre se ejecuta, omitiendo la mayoría de las instrucciones en el cuerpo del bucle. El caso intermedio anterior, sin ramas vectoriales, ejecuta todas las instrucciones vectoriales. El código final, con ramas vectoriales, ejecuta tanto la comparación como la rama en modo vectorial, lo que potencialmente mejora el rendimiento con respecto a la implementación escalar.

Vectorización manual

En la mayoría de los compiladores de C y C++ , es posible usar funciones intrínsecas para usar SIMD manualmente , a costa del esfuerzo del programador, la mantenibilidad y la portabilidad. Algunos lenguajes (por ejemplo, GNU C, C++ std::experimental::simd, Rust std::simd) incluyen tipos de datos vectoriales que se compilan a instrucciones SIMD apropiadas, lo que mejora la portabilidad y reduce el esfuerzo requerido. [ 8 ]

Otro enfoque es SPMD : escribir un programa que parezca operar con un solo elemento a la vez, y luego hacer que un compilador lo ensanche para que coincida con el ancho del vector SIMD. Este es el enfoque utilizado por los sombreadores gráficos y adoptado más recientemente por herramientas orientadas a la CPU como Intel IPSC. A diferencia de la vectorización automática, que puede fallar y recurrir a código escalar (por ejemplo, cuando hay una llamada a una función externa en un bucle), [ a ] SPMD garantiza que el resultado sea código vectorial cuando corresponda, de forma muy similar al uso manual de intrínsecas o tipos de datos vectoriales. [ 9 ]

Véase también

  • Página del proyecto de autovectorización del compilador GCC
  • Documentación sobre la autovectorización del compilador Clang / LLVM
  • Guía del desarrollador de vectorización automática del compilador Intel C++ Classic

Referencias

  1. Se ha trabajado en la solución del problema de las llamadas a funciones mediante convenciones de llamadas vectoriales y bibliotecas que utilizan dichas convenciones, como libmvec . [ 2 ] : § Vectorización de llamadas a funciones
  1. "ivdep" . Intel .
  2. 1 2 "Autovectorización en LLVM — Documentación de LLVM 23.0.0git" . llvm.org .
  3. ^ Holewinski, Justin; Ramamurthi, Ragavendar; Ravishankar, Mahesh; Fauzia, Naznin; Bolsa, Louis-Noël; Rountev, Atanas; Sadayappan, P. (6 de agosto de 2012). "Análisis dinámico basado en trazas del potencial de vectorización de aplicaciones". Avisos ACM SIGPLAN . 47 (6): 371– 382. doi : 10.1145/2345156.2254108 .
  4. Larsen, S.; Amarasinghe, S. (2000). "Explotación del paralelismo a nivel de superpalabra con conjuntos de instrucciones multimedia" . Actas de la conferencia ACM SIGPLAN sobre diseño e implementación de lenguajes de programación. ACM SIGPLAN Notices . 35 (5): 145– 156. doi : 10.1145/358438.349320 . hdl : 1721.1/86445 .
  5. "Optimización de código con compiladores IBM XL" (PDF) . Junio ​​de 2004. Archivado del original (PDF) el 10 de junio de 2010.
  6. Shin, J.; Hall, MW ; Chame, J. (2005). "Paralelismo a nivel de superpalabra en presencia de flujo de control". Actas del simposio internacional sobre generación y optimización de código . págs. 165–175 . doi : 10.1109/CGO.2005.33 . ISBN  0-7695-2298-X.
  7. Shin, J. (2007). "Introducción del flujo de control en el código vectorizado". Actas de la 16.ª Conferencia Internacional sobre Arquitectura Paralela y Técnicas de Compilación . págs. 280–291 . doi : 10.1109/PACT.2007.41 . 
  8. "Extensiones vectoriales" . Uso de la colección de compiladores GNU (GCC) . Consultado el 16 de enero de 2020 .
  9. Pharr, Matt; Mark, William R. (mayo de 2012). ispc: Un compilador SPMD para programación de CPU de alto rendimiento (PDF) . 2012 Innovative Parallel Computing (InPar). pp. 1–13 . doi : 10.1109/InPar.2012.6339601 . 
Obtenido de " https://en.wikipedia.org/w/index.php?title=Automatic_vectorization&oldid=1344433529 "