Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
FACULTAD DE INGENIERÍA 1 Examen 3: SAXPY Jorge Luis Téllez González, 315132726 Sistemas Distribuidos - Grupo: 01 Resumen—En este trabajo se analiza el rendimiento de diferentes ordenamientos de bucles anidados para la multiplicación de matrices empleando la función SAPXY que forma parte de la biblioteca BLAS. Se comparan los ordenamientos ijk, jik, ikj, jki, kij y kji en términos de su eficiencia, junto a una comparación entre 3 compiladores distintos y en ambientes operativos diferentes con el objetivo de identificar diferencias entre tiempos de ejecución. Index Terms—SAXPY, BLAS, matrices, multiplicación, eficiencia, memoria I. INTRODUCCIÓN SA XPY, acrónimo de Single-Precision A X Plus Y, esuna función de la biblioteca estándar de Basic Linear Algebra Subroutines (BLAS). En términos generales, SAXPY combina una multiplicación escalar y una suma de vectores. La multiplicación de matrices es una de las operaciones más importantes en diversas áreas de la ciencia y la ingenierı́a, y por tanto, la eficiencia a la hora de su ejecución resulta crı́tica para cumplir su propósito. Los arreglos multidimensionales (o matrices) son una estruc- tura de datos común en el cálculo cientı́fico para representar datos con múltiples dimensiones, como imágenes, señales de audio, volúmenes de datos, matrices de transformación, entre otros. Su importancia radica en que permiten un acceso y manipulación eficientes de los datos para realizar operaciones y cálculos en el cálculo cientı́fico. Sin embargo, el acceso ineficiente a los datos del arreglo puede ralentizar el procesa- miento y limitar la capacidad de los algoritmos para manejar grandes conjuntos de datos. Una de las formas más sencillas de implementar un cálculo matricial es observable en la Figura 1, la cual emplea tres ci- clos for anidados.Una ventaja de este método es su simplicidad y claridad; es fácil de entender y de implementar, lo que lo hace una buena opción para matrices pequeñas. Sin embargo, esta implementación no es eficiente para matrices grandes debido a la cantidad de accesos a memoria que requiere. En particular, la memoria caché no se utiliza de manera eficiente, lo que puede causar retrasos significativos en la ejecución del código. Figura 1: Algoritmo de multiplicación de matrices en C. El orden en que se recorren los vectores en el caché depende del lenguaje de programación utilizado. Por ejemplo, en el caso de C se utiliza el esquema de almacenamiento de matriz de orden-fila (Row major order), donde las filas se almacenan consecutivamente en la memoria. En cambio, Fortran utiliza el esquema de almacenamiento de matriz de orden-columna (Column major order), donde las columnas se almacenan consecutivamente en las lı́neas de caché. Figura 2: Row major order en C. En el caso del algoritmo anterior, una posible forma de mejorar la eficiencia de esta implementación es reordenando los bucles para que se ajusten a la forma en que se almacenan los elementos en la memoria caché. Al utilizar un orden de recorrido de las matrices que aprovecha la localidad espacial, se pueden reducir los tiempos de acceso a la memoria y, por lo tanto, mejorar la eficiencia de la operación de multiplicación de matrices. En el siguiente trabajo se implementará un programa en C que multiplique 2 matrices cuadradas y el resultado lo almacene en una tercera matriz. Posteriormente, se variará el orden de los bucles for para cronometrar el tiempo de ejecución entre cada variación; empleando sistemas de 100x100, 500x500 y 1000x1000 en cada una de ellas. Ası́ mismo, se verificará si existe alguna diferencia temporal al compilar empleando op- ciones distintas (g++, icx y Clang) o en entornos de ejecución distintos (Windows y Linux). Se proporciona un código de apoyo para la elaboración de la actividad, al cual se le han realizado ligeras modificaciones para su ejecución en Windows. Se anexan al trabajo presente los códigos empleados tanto en Windows como en ambientes Linux. FACULTAD DE INGENIERÍA 2 II. DESARROLLO Para el desarrollo de las actividades solicitadas se generó un programa que, a la entrada, pregunta el orden en que se desea ejecutar el cálculo matricial. Posteriormente, solicita el tamaño n de las matrices a multiplicar y, por último, arroja como salida el tiempo de llenado de las matrices y el tiempo que tomó realizar el cálculo matricial. Figura 3: Ejecución del programa en Windows para el caso ijk. Para el caso particular de Windows, en lugar de utilizar la biblioteca time se utiliza windows.h, la cual permite utilizar la API de Windows para obtener timestamps con funciones incorporadas al sistema operativo. A continuación, se mostrarán las ejecuciones de los 3 tamaños de matrices empleando los siguientes compiladores en el entorno de Windows: g++, icx y clang. Figura 4: Benchmarks de compiladores en Windows. El primer compilador usado corresponde a g++, un derivado de gdb que admite la compilación tanto de código C como de C++. Para la ejecución, se utilizó el entorno de Visual Studio Code. Por otra parte, la segunda y tercera elección correspondieron a ICX ( Intel® oneAPI DPC++/C++ Compiler, una versión actualizada del Intel® C++ Compiler Classic (ICC)) y Clang, un compilador multilenguaje pensado para su integración mejorada con IDEs como Visual Studio. Se anexan capturas para corroborar la instalación de estos compiladores en el equipo local para la ejecución de las pruebas. Figura 5: Instalación de g++. Figura 6: Instalación de icx. Figura 7: Instalación de Clang. La elección de estos compiladores se debió a los siguientes motivos: g++ es un compilador moderno incluido en todas las distribuciones estándar de Linux, de forma que no es necesario hacer ninguna configuración adicional para ejecutarlo en estos entornos. Por otra parte, su instala- ción resulta sencilla en Windows por medio del entorno MSYS2. icx o icpx es una biblioteca de cómputo cientı́fico y de alto rendimiento desarrollada por Intel, la cual incluye su propio compilador para C y C++. Clang es un compilador de C y C++ pensado para su integración en IDEs en entornos productivos como VS Code, y es la elección empleada en la academia de Computación Gráfica de la FI UNAM para el desarrollo de prácticas en OpenGL. La elección de los ambientes Linux consistieron en las dis- tribuciones de Linux Mint y Debian en sus versiones más recientes, en donde igualmente se instaló el compilador icx y Clang por medio de la consola. En las siguientes capturas se muestran probatorios de la ejecución de estos sistemas operativos por medio del software de virtualización VirtualBox y los resultados de los benchmarks con los 3 compiladores en ambos sistemas operativos Linux: FACULTAD DE INGENIERÍA 3 Figura 8: Ejecución del programa en la consola de Mint. Figura 9: Benchmarks de compiladores en Mint. Figura 10: Compilación del programa en la consola de De- bian. A partir de los datos recopilados se generaron una serie de gráficas comparativas en Excel de los 3 casos analizados: n = 100, n = 500 y n = 1000. Con estos datos plasmados gráficamente, se analizarán los resultados obtenidos para vi- sualizar tanto las diferencias en el acceso de los ı́ndices como en los entornos de ejecución y los compiladores utilizados para realizar los cálculos matriciales. Figura 11: Benchmarks de compiladores en Debian. II-A. Resultados obtenidos II-A1. Caso 1: A continuación se muestran los benchmarks gráficos para el primer caso de estudio: n = 100. Figura 12: Benchmark Caso 1. ijk: el primer caso analizado no demuestra diferencias significativas en tiempo de ejecución entre los distintos sistemas operativos empleando g++. Al observar los FACULTAD DE INGENIERÍA 4 resultados de icx se nota que el tiempo de ejecución se reduce de forma notable por la naturaleza del compilador; con W10 teniendo un tiempo de ejecución levemente mayor. Por último, en el caso de Clang el entornode W10 mostró una ligera ventaja frente a Debian; quedando notablemente atrás el tiempo marcado para Mint. ikj: g++ demuestra un ligero mejor rendimiento en W10, quedando en empate ambos sistemas Linux. Icx nueva- mente muestra un ligera ventaja para sistemas Linux, mientras que nuevamente Clang supera a los entornos Linux de forma notable. Este ordenamiento de ı́ndices representa una mejora notable con respecto al anterior. jik: en g++ Mint produce el resultado más lento, mientras que en icx el panorama el resultado empeora con respecto a su predecesor. Por último, se observa que Debian logra ponerse a la par de W10 a la hora de ejecutar el programa compilado con Clang. En general los resultados son semejantes a los casos anteriores. jki: g++ produce resultados similares a los vistos a jik, mientras que en icx se observa un delay notable en W10, cambiando las tablas y dejando a los sistemas Linux prácticamente a la par. Por último, Clang no arroja diferencias significativas con respecto a jik. kij: g++ demuestra resultados notablemente superiores en los 3 sistemas empleando esta forma de acceso; situación que se repite de forma notable en icx; pero que no resulta significativa en Clang. Mejora lo visto en ikj por cierto margen. kji: g++ muestra resultados ligeramente menores a los primeros 4 casos, sin embargo, en icx obtiene malos resultados como jki. En el caso de Clang no se observan diferencias notables. Es posible afirmar que, para un número n pequeño como el empleado, las diferencias entre las formas de ordenar los ı́ndices, si bien pueden tener un impacto en determinados compiladores, en otros la diferencia no resultará demasiado tangible; como lo demostró especı́ficamente Clang. Por otra parte, icx fue el compilador más sensible a las modificaciones en los accesos a los ı́ndices, en donde la combinación kij resultó ser la más rápida seguida de ikj, y esto se debe a que se ajusta de manera más efectiva a la forma en que la CPU accede a las lı́neas de caché. Debido a que se acceden a los elementos de una fila A y una columna B de manera consecutiva, este arreglo garantiza que los datos contenidos en los ı́ndices ya estén cargados en la caché, lo que disminuye la cantidad de veces que se requiere acceder a la memoria principal y, por lo tanto, aumenta el rendimiento de la operación de multiplicación de matrices. Por otra parte, jki y kji mostraron los peores resultados en cuanto a ejecución empleando icx; el compilador con mejores tiempos de respuesta. A partir de allı́, es posible observar que iniciar los ciclos en j y k puede tener un notable impacto en el acceso a memoria; siendo la peor combinación de todas jki por su ineficiencia a la hora de recuperar los datos de la memoria caché; generando en consecuencia un delay notable en las operaciones debido a que los datos no se recuperan eficientemente. Especı́ficamente, la recuperación secuencial de los elementos de la matriz en forma columnar provoca que las multiplicaciones no se puedan realizar en el momento debido a que todavı́a no se encuentran en memoria los siguientes valores para multiplicar. Resulta llamativo mencionar que Mint sufrió un delay consi- derable frente a sus competidores al ejecutar código compilado con Clang; hecho visible en las gráficas anteriores, sin embar- go, este comportamiento anómalo no se repitió conforme se incrementaron las entradas. Para los siguientes 2 casos se analizarán puntualmente los in- sights obtenidos por compilador y las diferencias con respecto al caso inicial. II-A2. Caso 2: A continuación se muestran los benchmarks gráficos para el segundo caso de estudio: n = 500. Figura 13: Benchmark Caso 2. FACULTAD DE INGENIERÍA 5 g++: Se obtiene una cota de ejecución superior a 0.6s. Las primeras 3 combinaciones arrojan resultados semejantes entre los 3 sistemas operativos, mientras que kij e ikj nuevamente arrojan los tiempos de ejecución más bajos. Por último, jki e kji arrojan los peores resultados. W10 se desempeñó peor en la mayor parte de casos, mientras que en el mejor logró aventajar ligeramente a los sistemas Linux. icx: Se obtiene una cota de ejecución superior de aproximadamente 0.24s; siendo el más rápido de los 3 compiladores. Las primeras 3 combinaciones arrojan resultados nuevamente semejantes entre los 3 sistemas operativos, mientras que kij obtiene tiempos de ejecución extremadamente rápidos (4x veces con respecto a los 2 peores casos), quedando ligeramente por detrás ikj. Las combinaciones jki e kji arrojan los peores resultados nuevamente. Nuevamente W10 arrojó peores resultados a excepción del mejor caso, en donde supera a sus competidores Linux. Clang: Se obtiene una cota de ejecución superior de apro- ximadamente 0.45s; siendo ligeramente superior a g++ pero quedando notablemente atrás de icx. La tendencia de g++ se repite en Clang, siendo kij e ikj los mejores caso, ijk y jik los casos regulares y jki y kji los peores casos. En este caso W10 lleva una ligera ventaja frente a los sistemas Linux, los cuales entre sı́ arrojan resultados muy semejantes. II-A3. Caso 3: A continuación se muestran los benchmarks gráficos para el último caso de estudio: n = 1000. g++: es apreciable que g++ presenta el peor rendimiento entre los 3 compiladores. Entre los sistemas operativos, Debian mostró los resultados más lentos, mientras que Windows 10 destacó como el SO más rápido al arrojar los resultados. Se obtuvo una cota superior cercana a 5.5s. La superioridad de ikj y kji resulta evidente, dejando atrás al resto de combinaciones notablemente; patrón que se repite en el resto de compiladores. icx: Se obtiene una cota de ejecución superior de apro- ximadamente 2.7s. Icx es aproximadamente 2 veces más rápido frente a sus competidores. Aunque W10 obtiene peores resultados en los casos regulares y malos, en el mejor caso W10 superó por completo a los sistemas Linux. Entre estos sistemas, Mint se desempeñó de mejor forma. Clang: Se obtiene una cota de ejecución superior de aproximadamente 5s; repitiendo su ligera superioridad a g++ pero quedando atrás de icx. W10 fue el mejor SO empleando este compilador, mientras que Debian fue el peor; quedando Mint en una posición intermedia. III. CONCLUSIONES 1. Por medio de los resultados obtenidos es posible afirmar que kij representa el mejor caso, ikj el segundo mejor caso, ijk, y jik los casos regulares y jki y kji los Figura 14: Benchmark Caso 3. peores casos; tendencia que se hace más visible conforme aumentan los tamaños de las matrices cuadradas. 2. W10 mostró resultados dispares: en los casos regulares y malos tuvo un desempeño inferior frente a los sistemas Linux, mientras que en el mejor caso logra igualar o superar a sus competidores. 3. Mint mostró ser la mejor distribución Linux en cuanto a desempeño conforme se aumentó el tamaño de las matrices cuadradas, arrojando resultados consistentes que le otorgaron el segundo lugar en diversos casos de prueba en cuanto a rendimiento. En el primer caso de prueba esta tendencia resultaba ser al revés: siendo más lento en comparación a Debian y mostrando inconsistencias en el caso inicial. 4. Debian fue el sistema operativo con tiempos de ejecución más largos conforme se aumentaban los tamaños de las matrices; únicamente mostrando superioridad frente a FACULTAD DE INGENIERÍA 6 Mint y W10 en el caso n = 100 y quedando como el SO más lento en n = 1000 empleando g++ y Clang; siendo W10 el más lento por poco margen usando icx. 5. g++ fue el compilador con los peores resultados a través de los 3 sistemas operativos, obteniendo resultados dispares entre los 3 sistemas operativos conforme se modificaba n. 6. icx es el compilador que tuve mejor desempeño en las pruebas; especialmente en el mejor caso en donde obtenı́a mejores de 2 a 4 veces con respecto a sus competidores. En los tres sistemas operativos icx mejoró notablemente los tiempos de ejecución. 7. Clang en general mostró cierta superioridadcon respecto a g++ en todas las pruebas; quedando completamente atrás de icx, sin embargo. Ası́ mismo, W10 resultó el mejor SO para emplear este compilador, arrojando en los 3 casos de prueba los mejores resultados por cada combinación. Los resultados indican que la forma en que se organizan los bucles anidados puede tener un gran impacto en el rendimiento de los algoritmos de multiplicación de matrices implementados en C, ya que C utiliza el ordenamiento Row major order para el acceso a los ı́ndices almacenados en memoria caché. En particular, se encontró que la combinación kij es la más eficiente para la multiplicación de matrices; hecho reflejado en los sistemas operativos y los compiladores empleados. Este resultado se encuentra acorde a lo referenciado por Bryant y O’Hallaron, en donde se llegó a la misma conclusión en cuanto a la eficiencia de kij e ikj. Figura 15: Prueba con un Core i7. Como se discutió previamente, esta combinación resulta ópti- ma con respecto a la forma en que se almacenan los datos en la memoria caché, lo que les permite acceder rápidamente a los elementos de la matriz. Por otro lado, las combinaciones de ı́ndices jki y kji resultaron ser las peores combinaciones para la multiplicación de matrices, ya que resultaron en una gran cantidad de delays que afectaron significativamente el rendimiento del algoritmo. Por último, es posible afirmar que las diferencia entre siste- mas operativos no son determinantes cuando se ejecutan los primeros 3 casos regulares, sin embargo, en el mejor caso y los peores casos se observan diferencias significativas entre sistemas operativos. Ası́ mismo, los resultados pueden variar entre compiladores, de forma que si se obtuvo una tendencia entre SO empleando g++, esta puede resultar completamente distinta al utilizar otro compilador, por lo que es posible afir- mar que el factor más determinante para el rendimiento de este algoritmo se encuentra en el compilador, y no en el sistema operativo. Cabe señalar que, en el último caso, se observan las ineficiencias del algoritmo empleado, el cual al utilizar tres ciclos for resulta en una complejidad computacional del orden de O(n)3. Para mostrar esta ineficiencia, se ejecutó el mejor caso con n = 10000, lo que arrojó los siguientes resultados: Figura 16: Prueba con valores superiores. En comparación con los cálculos, el rellenado inicial de las matrices al ser de un orden inferior de O(n)2 no arrojó resultados tan elevados, sin embargo, en la práctica siguen siendo resultados pobres que, en presencia de cálculos de aún mayor complejidad como los realizados en despliegues gráficos, resultarı́an en tiempos de procesamiento demasiado largos e ineficientes. Finalmente, con los resultados obtenidos se hace patente la necesidad de comprender los pormenores de la arquitectura de los procesadores en cuanto a su manejo de la memoria y cómo aprovecharla para mejorar la ejecución de determinados algoritmos. Sin embargo, por más estrategias que se utilicen en este sentido el resultado final lo determinará el propio al- goritmo y su complejidad computacional. Combinando ambos enfoques, es posible obtener soluciones a la medida y capaces de tratar con grandes volúmenes de información sin retrasos considerables de tiempo. BIBLIOGRAFÍA [1] Intel. “Get Started with the Intel oneAPI DPC++/C++ Compiler.” (), dirección: https://www.intel.com/content/ www/us/en/docs/dpcpp-cpp-compiler/get-started-guide/ 2023-0/overview.html (visitado 18-03-2023). [2] LLVM. “Getting Started: Building and Running Clang.” (), dirección: https : / / clang . llvm . org / get started . html (visitado 18-03-2023). [3] D. Tarnoff. “CSCI 4717 - High Performance Counter.” (), dirección: https://faculty.etsu.edu/tarnoff/labs4717/ performance/hpc.htm (visitado 18-03-2023). https://www.intel.com/content/www/us/en/docs/dpcpp-cpp-compiler/get-started-guide/2023-0/overview.html https://www.intel.com/content/www/us/en/docs/dpcpp-cpp-compiler/get-started-guide/2023-0/overview.html https://www.intel.com/content/www/us/en/docs/dpcpp-cpp-compiler/get-started-guide/2023-0/overview.html https://clang.llvm.org/get_started.html https://faculty.etsu.edu/tarnoff/labs4717/performance/hpc.htm https://faculty.etsu.edu/tarnoff/labs4717/performance/hpc.htm FACULTAD DE INGENIERÍA 7 [4] R. E. Bryant y D. R. O’Hallaron. “Cache Memory and Performance.” (2023), dirección: https: / /courses.cs .vt . edu/cs2506/Spring2020/notes/L16 CacheAndCoding.pdf (visitado 18-03-2023). Los créditos de las fotografı́as pertenecen a sus autores. © https://courses.cs.vt.edu/cs2506/Spring2020/notes/L16_CacheAndCoding.pdf https://courses.cs.vt.edu/cs2506/Spring2020/notes/L16_CacheAndCoding.pdf Introducción Desarrollo Resultados obtenidos Caso 1 Caso 2 Caso 3 Conclusiones
Compartir