Incremento en el desempeño de la multiplicación Matriz-Vector

11
Incremento en el Desempeño en la Multiplicación Matriz- Vector Sotero Ordoñes Nogales 1 , José Antonio Muñoz Gómez 1 , Abimael Jiménez Pérez 2 1 Departamento de Ingenierías, Universidad de Guadalajara campus CUCSur, Autlán de la Grana, México [email protected], [email protected] 2 Instituto de Ingeniería y Tecnología, Universidad Autónoma de Ciudad Juárez, Ciudad Juárez, México [email protected] Abstract. This paper is focused on the analysis of several optimization tech- niques of serial and parallel code in order to increase the computational perfor- mance of matrix-vector multiplication. The paper presents both an exhaustive study of described techniques, and the features of coding in detail. Several nu- merical studies were done, where the characteristics that must have the algo- rithm to get the best computational performance with one core and multi-core processing using shared memory with OpenMP were determined. In particular, we study exhaustively the influence in the computational performance employ- ing the technique of loop unrolling and vector data processing. Numerically was demonstrated that the best performance is obtained with an unroll-8, getting a gain of 1.6x, and using parallel registers a gain of 2.2x is achieved. In addition, using parallel programming were determined 4.5, 8.2 and 9.8 Gflops with 2, 4 and 8 cores, respectively. Resumen. Este trabajo se concentra en el análisis de diversas técnicas de optimización de código en serie y en paralelo para incrementar el desempeño computacional de la multiplicación matriz-vector. Se presenta de forma detallada el estudio exhaustivo de las técnicas descritas así como los detalles de codificación. Se realizan diversos estudios numéricos en donde se determinan las características que debe tener el algoritmo para obtener el mayor rendimiento computacional en un solo núcleo y con varios núcleos de procesamiento empleando memoria compartida con OpenMP. En particular, se estudia exhaustivamente la influencia en el desempeño computacional empleando la técnica de loop unrolling y el procesamiento vectorial de datos. Mostramos numéricamente que el mejor desempeño se obtiene con unroll-8 obteniendo una ganancia de 1.6x, y empleando los registros paralelos se alcanza 2.2x. Además, empleando la programación en paralelo se determinaron 4.5, 8.2 y 9.8 Gflops 2,4,8 núcleos respectivamente. Keywords: cómputo paralelo, computación científica, técnicas de unroll, memoria compartida, OpenMP.

Transcript of Incremento en el desempeño de la multiplicación Matriz-Vector

Page 1: Incremento en el desempeño de la multiplicación Matriz-Vector

Incremento en el Desempeño en la Multiplicación Matriz-Vector

Sotero Ordoñes Nogales1, José Antonio Muñoz Gómez1, Abimael Jiménez Pérez2

1Departamento de Ingenierías, Universidad de Guadalajara campus CUCSur, Autlán de la Grana, México

[email protected], [email protected] 2Instituto de Ingeniería y Tecnología, Universidad Autónoma de Ciudad Juárez, Ciudad Juárez,

México [email protected]

Abstract. This paper is focused on the analysis of several optimization tech-niques of serial and parallel code in order to increase the computational perfor-mance of matrix-vector multiplication. The paper presents both an exhaustive study of described techniques, and the features of coding in detail. Several nu-merical studies were done, where the characteristics that must have the algo-rithm to get the best computational performance with one core and multi-core processing using shared memory with OpenMP were determined. In particular, we study exhaustively the influence in the computational performance employ-ing the technique of loop unrolling and vector data processing. Numerically was demonstrated that the best performance is obtained with an unroll-8, getting a gain of 1.6x, and using parallel registers a gain of 2.2x is achieved. In addition, using parallel programming were determined 4.5, 8.2 and 9.8 Gflops with 2, 4 and 8 cores, respectively. Resumen. Este trabajo se concentra en el análisis de diversas técnicas de optimización de código en serie y en paralelo para incrementar el desempeño computacional de la multiplicación matriz-vector. Se presenta de forma detallada el estudio exhaustivo de las técnicas descritas así como los detalles de codificación. Se realizan diversos estudios numéricos en donde se determinan las características que debe tener el algoritmo para obtener el mayor rendimiento computacional en un solo núcleo y con varios núcleos de procesamiento empleando memoria compartida con OpenMP. En particular, se estudia exhaustivamente la influencia en el desempeño computacional empleando la técnica de loop unrolling y el procesamiento vectorial de datos. Mostramos numéricamente que el mejor desempeño se obtiene con unroll-8 obteniendo una ganancia de 1.6x, y empleando los registros paralelos se alcanza 2.2x. Además, empleando la programación en paralelo se determinaron 4.5, 8.2 y 9.8 Gflops 2,4,8 núcleos respectivamente.

Keywords: cómputo paralelo, computación científica, técnicas de unroll, memoria compartida, OpenMP.

Page 2: Incremento en el desempeño de la multiplicación Matriz-Vector

1 Introducción

Una de las operaciones algebraicas más importantes en computación científica es la multiplicación de una matriz por un vector. Esta operación es el núcleo computacional de los esquemas iterativos modernos basados en sub-espacios de Krylov, que nos permiten resolver de manera eficiente diversos problemas reales en las ciencias y las ingenierías [1]. La operación matriz-vector tienen un orden de complejidad cuadrático !(!!), para valores de suficientemente grandes o bien cuando se realizan miles de iteraciones es requerido optimizar el código para incrementar el desempeño computacional. Desde un punto de vista matemático la multiplicación matriz-vector se expresa como:

!" (1)

donde ! es una matriz de !×! y ! es un vector con ! elementos definidos sobre el campo de los números reales. La sección de código que implementa a (1) se muestra a continuación.

/* ----- Ax version canónica ----- */ for(i=0; i < n; i++){ for(j=0, c[i] = 0; j < n; j++) c[i] = c[i] + A[i][j] * x[j]; }

Esta forma de codificación corresponde a la forma básica o canónica de implementar la operación, la cual es correcta desde el punto de vista del álgebra matricial, pero deficiente en el área de computación científica. Esta forma de implementación tiene un bajo desempeño computacional. Existen una gran variedad de bibliotecas de código libre dedicadas a la optimización de operaciones del álgebra matricial. En particular, la multiplicación matriz-vector se puede encontrar en BLAS (Basic Linear Algebra Subprograms) y ATLAS (Automatically Tuned Linear Algebra Software): la primera consiste en un conjunto de funciones altamente optimizadas [2], en la segunda se agregan técnicas para la optimización automática [3]. Ambas bibliotecas plantean un escenario centrado en la generación de código de alto rendimiento y la reducción de tiempo para el desarrollo de la aplicación, mediante la generación de subrutinas y bibliotecas con entornos adaptables. Por su parte las bibliotecas, PLASMA (Parallel Linear Algebra Software for Multiprocessor Architectures) y FLAME (Formal Linear Algebra Methods Environment), se enfocan en la optimización automática de aplicaciones con un fuerte enfoque en la arquitectura de la computadora: mediante la abstracción del hardware y del sistema operativo. Sin embargo, el reto se complica con el hermetismo y con la programación paralela para las arquitecturas multi-núcleo, las cuales presentan características distintas de rendimiento. Aunado a ello la complejidad para operar dichas bibliotecas crece considerablemente. Cabe hacer mención, que en estas bibliotecas numéricas, algunas optimizaciones (relacionadas con la multiplicación matriz-vector) se eligen de forma automática [2, 3]. Sin embargo, en aplicaciones

Page 3: Incremento en el desempeño de la multiplicación Matriz-Vector

específicas y considerando la arquitectura de la computadora, es posible realizar un mejor trabajo de optimización de código [4]. En este trabajo se abordan distintas técnicas computacionales de optimización de código para obtener el máximo rendimiento en la multiplicación matriz-vector con matrices densas, empleando uno o varios núcleos de procesamiento. Este tipo de matrices surgen de manera natural en procesos de interpolación con funciones radiales [5] y en la solución numérica de ecuaciones diferenciales parciales empleando funciones de soporte global [6]. En particular, se utiliza la técnica loop unrolling y el procesamiento vectorial para mejorar el rendimiento computacional en un solo núcleo. Se muestra experimentalmente que la velocidad de ejecución mejora significativamente con el uso de estas técnicas de programación avanzadas. Para explotar la arquitectura multi-núcleo con memoria compartida, se programaron funciones paralelas con OpenMP (Open Multi-Processing) optimizadas con las dos técnicas antes mencionadas. Obteniendo una escalabilidad lineal hasta cuatro núcleos y un factor de 5.5x con ocho núcleos de procesamiento. En la siguiente sección se describe la técnica de loop unrolling y se determina experimentalmente la profundidad óptima del unroll (U). En la sección 3 se aborda la programación paralela a nivel de datos empleando los registros vectoriales de Intel. En la sección 4, se desarrolla la multiplicación matriz-vector con un enfoque de cómputo paralelo basado en el paradigma de memoria compartida con API (Application Program Interface) o interfaz de programación de aplicaciones de OpenMP. En esta sección, se fusionan las técnicas tratadas en las secciones 2-3, para obtener finalmente la implementación con el mayor desempeño computacional. Por último, se enuncian las conclusiones del presente trabajo.

2 Loop unrolling

Los ciclos de procesamiento son los responsables de la mayoría del tiempo de ejecución en muchos tipos de aplicaciones. Las aplicaciones científicas pueden ser caracterizadas con un consumo mayor al 90% de su tiempo de ejecución en uno o pocos ciclos de procesamiento [7]. La compilación con técnicas automáticas de unroll es comúnmente usado en programas científicos, y se pueden obtener grandes beneficios en tiempo de procesamiento [8, 9]. Sin embargo, no existe un compilador que incremente el desempeño computacional para cualquier tipo de programa científico. Por ello, la necesidad de realizar codificaciones y mejoras no automáticas para incrementar el desempeño computacional. Esta técnica computacional consiste en disminuir el número de iteraciones de un ciclo mediante un aumento de operaciones en el mismo ciclo. La idea radica en desdoblar el ciclo para tratar de incrementar el desempeño computacional mediante el procesamiento paralelo a nivel de máquina subyacente en la arquitectura pipeline de los procesadores [9]. Para ejemplificar la técnica de unroll, consideremos el producto punto entre un par de vectores

!!!!!!!! (2)

Page 4: Incremento en el desempeño de la multiplicación Matriz-Vector

lo cual constituye el núcleo de la multiplicación matriz-vector. El método de unroll aplicado a (2) se puede expresar como:

!!!! + !!!!!!!!          !!!!!!!! (3)

!!!! + !!!!!!!! + !!!!!!!! + !!!!!!!!          !!!!!!!! (4)

donde Δ! representa el factor de incremento sobre el índice del ciclo y corresponde al valor de profundidad del unroll. En las ecuaciones (3) y (4) se tiene un nivel de anidamiento de orden 2 y 4 respectivamente. Cabe señalar que desde el punto de vista algebraico, el producto punto se puede expresar de distintas maneras (2-4). Sin embargo, dentro del marco de las ciencias computacionales se tienen rendimientos diferentes. Es conocido, que el nivel de profundidad óptimo del unroll depende de la arquitectura, banderas de compilación y uso eficiente de la memoria caché utilizada [2]. En particular es de interés determinar el mejor valor de unroll para la multiplicación matriz-vector. Para ello, seleccionamos distintos factores de unroll-{1,2,…,20}, ! = 4096, y compilación sin banderas. Los vectores contienen números aleatorios de distribución uniforme en el rango [1,1]. En la figura 1 se muestra la curva del tiempo de ejecución para el producto matriz-vector empleando distintos factores de unroll. Como se observa en la figura anterior, conforme incrementamos el factor de unroll el tiempo de procesamiento disminuye drásticamente y a partir de unroll-8 (U-8), el tiempo se estabiliza. Es decir, la máxima ganancia se obtiene en dicho factor de unroll. Un resultado similar se obtiene para distintos valores de !.

Fig. 1. Multiplicación matriz-vector con unroll.

3 Intel MMX

La fuerte demanda de recursos de las aplicaciones de multimedia y de comunicaciones dio inicio al desarrollo de nuevas tecnologías que permitieran cubrir

Page 5: Incremento en el desempeño de la multiplicación Matriz-Vector

esas necesidades. Así es como vio la luz Intel MMX que se incorporó a la arquitectura Intel en 1997. Esta nueva incorporación estaba destinada principalmente al procesamiento vectorial con operaciones principalmente aritméticas sobre vectores. La nueva tecnología fue construida bajo el esquema SIMD (Single Instruction, Multiple Data) o una instrucción para múltiples datos [10]. Es decir, se aplica una misma instrucción/operación sobre un conjunto de datos; esto permite conseguir paralelismo a nivel de datos mediante el procesamiento vectorial. Según datos del fabricante, los núcleos de procesamiento escritos con MMX presentan un incremento en la velocidad de ejecución a razón de 1.5x y hasta 2.x [11]. En la figura 2 se muestra el modelo de ejecución para sistemas SIMD. A partir de la figura, se puede deducir que el sistema funciona de forma síncrona donde intervienen Unidades de Procesamiento (UP) y una Unidad de Control (UC), la cual supervisa y asigna las operaciones a realizar en las UPs. La UC es la responsable de enviar la misma instrucción a las UPs; cada UP trabaja en un subconjunto de los datos. El código en Lenguaje C corresponde a la multiplicación de una matriz por un vector con MMX mediante el uso de las funciones intrínsecas. Cabe mencionar que en ésta función únicamente se utiliza un registro XMM, con pequeñas modificaciones se pueden utilizar todos los registros.1

/* ----- Ax con Intel MMX ----- */ void mv_mmx(float **A, int n, float *x, \ float *b){ __m128 n1, n2, n3, n4; for(int i = 0; i < n; i++) { /* Los cuatro elementos del mini-vector se igualan a cero.*/ n4 = _mm_setzero_ps(); for(int j = 0; j < n; j += 4) { /*Se cargan los cuatro elementos consecutivos del i-ésimo renglón a partir del j-ésimo ele- mento de A */ n1 = _mm_loadu_ps(A[i]+j); n2 = _mm_loadu_ps(x+j); /* Se multiplican los dos mini-vectores*/ n3 = _mm_mul_ps(n1, n2); /* Se realiza la suma horizontal */ /* Se realiza una suma vertical y el resultado es almacenado temporalmente en n4 */ n3 = _mm_hadd_ps(n3, n3); n4 = _mm_add_ps(n4, n3); }

1 Más información referente a la programación MMX y sus registros en Intel® Developer

Services Inc. MMXTM technology developers guide. Marzo de 1996.

Page 6: Incremento en el desempeño de la multiplicación Matriz-Vector

/* Se realiza una última suma horizontal para guar dar el resultado en el primer elemento del mini- vector n4 */ n4 = _mm_hadd_ps(n4, n4); _mm_store_ss(&b[i],n4); } }

Fig. 2. Modelo de ejecución de Intel MMX.

Con base en los datos técnicos del fabricante, la tecnología MMX inicialmente tenía ocho registros estáticos de 64 bits de longitud, principalmente para procesamiento de números enteros. Posteriormente, se extendió con la adición de Streaming SIMD Extensions incorporando ocho nuevos registros capaces de trabajar con números en punto flotante nombrados XMM [12]. Hasta donde tenemos conocimiento, se cargan cuatro números IEEE 754 en cada registro XMM. De esta manera cargamos cuatro elementos de cada vector y los procesamos de forma paralela. Este comportamiento es análogo a procesar con unroll-4 (U-4) en paralelo a nivel de datos. Ahora, es de nuestro interés determinar el desempeño computacional empleando la técnica de unroll vs. MMX. En la figura 3 se muestra una comparación del rendimiento de la multiplicación matriz-vector con tres versiones: canónica, unroll y MMX, empleando los mismos parámetros de ejecución de la sección anterior. En esta gráfica se observan dos puntos principales: a) el tiempo de ejecución de la versión MMX es aproximadamente 2.2x veces más rápido que la versión canónica, b) el rendimiento con la programación en paralelo de Intel es superior a las técnicas de unroll desarrolladas. En particular, se determinó una ganancia de 1.5x y 1.4x con respecto de U-8 y U-4 respectivamente. Estos resultados son congruentes con lo reportado por parte de Intel.

Page 7: Incremento en el desempeño de la multiplicación Matriz-Vector

Fig. 3. Tiempos para distintas optimizaciones.

4 OpenMP

Históricamente la programación paralela era un proceso dependiente de la arquitectura de la computadora, se necesitaba un gran esfuerzo y el código resultante no era portable, es decir no podría usarse en una máquina distinta. Hace una década cuando se popularizaron los procesadores con múltiples núcleos da inicio a una serie de paradigmas de programación enfocados a la memoria compartida [13]. Recientemente, el paradigma de OpenMP ha presentado un fuerte impulso debido a que es un API estándar disponible para FORTRAN y C, y que de manera sencilla se pueden paralelizar ciclos de procesamiento obteniendo una ganancia aceptable [14].

Fig. 4. Ejecución en multi-núcleo.

En nuestro caso, la multiplicación de una matriz por un vector tiene paralelismo inherente. Esto se puede observar en la figura 4 donde un color oscuro representa la multiplicación del vector ! por un renglón de la matriz !. Es fácil deducir que esta misma operación se puede ejecutar de manera simultánea empleando varios núcleos de procesamiento. La forma de implementar esta operación con OpenMP se muestra a continuación.

Page 8: Incremento en el desempeño de la multiplicación Matriz-Vector

/* ----- Ax canónica con Openmp ----- */ void omp_multiply_mv(float *A, float *x, float *b, int n) { int i, j; float tmp; #pragma parallel omp for default(none) private(i,j,tmp) \ shared(A,b,x,n) for(i = 0; i < n; i++){ tmp = 0f; for(j = 0; j < n; j++) tmp = tmp + A[i*n+j]*x[j]; b[i] = tmp; } }

Cabe hacer mención que con OpenMP podemos paralelizar de una manera rápida los ciclos iterativos, obteniendo un rendimiento aceptable. Sin embargo, la programación de software portable, rápido, estable y de alto desempeño para las computadoras paralelas de memoria compartida sigue siendo un gran desafío. En el libro de Chapman [15], se aborda la multiplicación matriz-vector de manera exhaustiva en donde se explican diversas técnicas para implementar esta operación algebraica. Los resultados obtenidos en esta sección son comparables a los reportados en dicha referencia.

(a)

(b)

Page 9: Incremento en el desempeño de la multiplicación Matriz-Vector

(c)

(d)

Fig. 5. MMX vs. Unroll en paralelo con OpenMP.

En la figura 5 se muestra el rendimiento computacional de la multiplicación matriz-vector para la ejecución paralela empleando las dos técnicas de optimización tratadas (loop unrolling y MMX). En el experimento se evaluaron un total de 20 codificaciones diferentes: unroll{2,3,…,16} y MMX{1,2,3,4}. En las gráficas a,b no se utilizaron banderas de compilación y, en las figuras c,d se utilizó la bandera –O2. Para simplificar la discusión sólo se despliegan las figuras con el mejor desempeño computacional de cada técnica.

Por otro lado, en el experimento donde se utilizó la bandera de compilación –O2 se aprecian dos puntos muy importantes: 1) en todas las codificaciones el rendimiento aumentó en un promedio de 3x con respecto a la compilación sin banderas, y 2) la escalabilidad en la técnica de unroll se mantuvo como en el caso anterior, no así para la versión de MMX. Cabe señalar, que las gráficas de escalabilidad y el rendimiento con 8 núcleos para los códigos MMX1-4 son equivalentes, es decir, no existe ganancia alguna por utilizar más de un registro XMM.

Tomando en cuenta estos resultados se concluye lo siguiente: 1) la técnica Intel MMX tiene un mejor desempeño computacional, y 2) la técnica de unroll tiene una mejor escalabilidad paralela. Estas condiciones persisten con el uso de banderas de compilación. Por ello se recomienda el uso de éstas banderas en la compilación de código para obtener mejores resultados en rendimiento.

5 Conclusiones

En este trabajo realizamos un estudio exhaustivo de la multiplicación matriz-vector desde un punto de vista computacional, con el objetivo de incrementar el desempeño. Se programaron y analizaron diferentes propuestas que explotan las líneas de caché, los registros XMM y la arquitectura multi-núcleo con memoria compartida. Realizamos dos propuestas para acelerar la multiplicación matriz-vector en serie y en paralelo. La primera consiste en utilizar la tecnología Intel MMXTM y la segunda en emplear la técnica de loop unrolling con un factor de profundidad igual a ocho. Cabe

Page 10: Incremento en el desempeño de la multiplicación Matriz-Vector

señalar, que esta última propuesta no necesita hardware especial a diferencia de la tecnología MMXTM y, ambas son independientes del sistema operativo. En el primer experimento numérico, obtuvimos un rendimiento de 0.5 Gflops con la técnica de Unroll-8 y de 0.7 Gflops empleando MMX, empleando un solo núcleo (en serie) y sin banderas de compilación. En paralelo con memoria compartida (OpenMP), la mejor escalabilidad paralela se obtiene con la técnica loop unrolling obteniendo factores de 3.8x y 5.1x para 4 y 8 núcleos respectivamente. Estos resultados no son afectados por las banderas de compilación. Cabe mencionar, que la gran diferencia entre nuestro resultado de 5.1x (para 8 núcleos) y lo ideal (8x) se debe a los problemas con la gestión de la memoria caché. Por otro lado, el mejor rendimiento en serie y en paralelo se obtiene al utilizar los registros del sistema Intel MMXTM y compilando con banderas de optimización. Se obtuvieron rendimientos de 4.5 Gflops, 8.2 Gflops y 9.8 Gflops para 2, 4, 8 núcleos respectivamente. Como trabajo futuro se contempla dividir en matrices más pequeñas para procesarla en paralelo a nivel de datos, con el objeto de reutilizar los elementos del vector en diferentes niveles de caché.

6 Referencias

1. J. Liesen, Z. Strakos, Krylov subspace methods: principles and analysis, Oxford Universi-ty Press, 2013.

2. R. C. Whaley and J.J. Dongarra, Automatically tuned linear algebra software, University of Tennessee, TN, technical report, December 1997.

3. C. L. Lawson, R. J. Hanson, D. R. Kincaid, and F. T. Krogh. 1979. “Basic linear algebra subprograms for fortran usage”, ACM Transactions mathematical software vol. 5, Issue 3, pp. 308-323, September 1979.

4. Intel® math kernel library 11.0 updates 3, reference manual, 2013. 5. M. D. Buhann, Radial basis functions, Cambridge University Press, 2003. 6. P. Gonzalez-Casanova, J. A. Muñoz-Gómez, G. Rodríguez-Gómez, “Node adaptive do-

main decomposition method by radial basis functions”, Numerical Methods for Partial Dif-ferential Equations, vol. 25, issue 6, pages 1482-1501, 2009.

7. J. L. Hennessy and D. A. Patterson, Computer architecture: a quantitative approach, 2nd ed., Morgan Kaufmann, 1995.

8. A. Aiken and A. Nicolau, Loop quantization: an analysis and algorithm, technical report, Cornell University, Ithaca, march 1987.

9. J. Davidson and S. Jinturkar, “Improving instruction-level parallelism by loop unrolling and dynamic memory disambiguation”, in Proc. of the 28th Annual international symposi-um on microarchitecture, New York, NY, pp. 125–132, 1995.

10. M. Mittal, A. Peleg, and U. Weiser, “MMXTM technology architecture overview”, Intel Technology Journal, 3rd Quarter 1997.

11. A. Peleg, U. Weiser, “MMXTM technology extension to the Intel architecture”, IEEE Mi-cro, Vol. 16, No. 4, pp. 42-50, August 1996.

12. Intel® 64 and IA-32 architectures optimization reference manual. Vol. A. Intel Inc. April 2012.

13. H. Sutter, J. Larus, “Software and the concurrency revolution”, Queue, Vol. 3, No. 7, pp. 54-62, September 2005.

14. J. A. Muñoz-Gómez, A. Jiménez-Pérez, “Súper-cómputo personal”, Cuadernos fronterizos de la Universidad Autónoma de Ciudad Juárez, vol. 1, pp. 13-16, 2011.

Page 11: Incremento en el desempeño de la multiplicación Matriz-Vector

15. B. Chapman, G. Jost, and R. Pas, Using OpenMP: portable shared memory parallel pro-gramming, scientific and engineering computation, MIT Press, 2007.