-
Notifications
You must be signed in to change notification settings - Fork 0
Práctica 6: Paralelismo multinivel: vectorización y ejecución multinúcleo
El objetivo principal de esta práctica es combinar paralelismo a nivel vectorial (AVX) con paralelismo a nivel de núcleo (OpenMP). Vamos a paralelizar con directivas OpenMP un bucle sencillo del que generaremos una versión escalar y otra vectorial (AVX). Compararemos las prestaciones de ambos códigos y analizaremos cómo escalan con el número de threads. También realizaremos distintos análisis del código para obtener métricas sencillas que nos permitan caracterizar su ejecución.
- Requerimientos hardware y software:
- CPU con soporte de la extensión vectorial AVX
- SO Linux Los equipos del laboratorio L0.04 cumplen los requisitos indicados. Puede trabajarse en dichos equipos de forma presencial y también de forma remota si hay alguno arrancado con Linux. Los ficheros que utilizaremos en esta sesión de prácticas se pueden encontrar en la carpeta ´p6´del repositorio:
git clone https://github.com/universidad-zaragoza/Multiprocesadores.git
- Inicializa la variable de entorno CPU. Se utiliza para organizar los experimentos realizados en distintas máquinas
en distintos directorios.
$ source ./init_cpuname.sh
Vamos a realizar un análisis estático del código con la herramienta Intel Architecture Code Analyzer (IACA).
https://software.intel.com/en-us/articles/intel-architecture-code-analyzer
IACA analiza de forma estática pequeños fragmentos de código (kernels) para varias microarquitecturas de Intel (Haswell, Broadwell, Skylake).
Para un código dado, IACA:
- Identifica la asociación entre las instrucciones y los puertos del procesador en condiciones ideales del front-end, núcleo de ejecución fuera de orden y jerarquía de memoria.
- Realiza un análisis estático del throughput del kernel y muestra su número de ciclos de ejecución.
Puedes encontrar más información acerca de IACA y su uso en el siguiente enlace:
http://stackoverflow.com/questions/26021337/what-is-iaca-and-how-do-i-use-it
-
Observa en el fichero triad.c los marcadores IACA_START e IACA_END que se han insertado para indicar a la herramienta el inicio y el final del código a analizar. El valor de dichos marcadores está en el fichero
/usr/local/include/iacaMarks.h
. -
Analiza y comprende el contenido del fichero
comp.iaca.sh
. -
Compila con soporte IACA la versión escalar (noavx) del programa triad.c:
$ ./comp.iaca.sh
-
Observa los ficheros que contienen el ensamblador del binario generado y localizar los marcadores IACA_START e IACA_END. ¿Cuál es la secuencia de bytes asociada a cada marcador? Nota: estos marcadores impiden la vectorización del código. Ver por ejemplo: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63467
-
Ejecuta el binario generado:
$ ./triad.noavx.single.gcc.iaca
Parece que hay algún problema. Vamos a ver si identificamos la causa de dicho problema. ¿En qué registro se guarda el iterador del bucle externo (nl)? ¿Qué registro modifica el código de los marcadores IACA_START e IACA_END?
-
Analiza y comprende el contenido del fichero run.iaca.sh.
-
Ejecuta IACA para realizar el análisis estático del bucle:
$ ./run.iaca.sh
- Analiza los informes de throughput generados:
- ¿Cuál es el throughput del bucle en ciclos?
- ¿Cuál es el cuello de botella del bucle?
- ¿Cuáles son los puertos más utilizados?
- Analiza la traza generada:
- Identifica las instrucciones que generan dos uops, y observa la dependencia entre ambas.
- Identifica las dependencias entre instrucciones
- Optativo. Repetir los apartados anteriores con la versión vectorial (avx) de triad.c. Utiliza para compilar con soporte IACA el script comp.iaca.avx.sh.
-
Especificar de forma manual el paralelismo existente en el bucle principal del programa. Para ello, inserta las directivas OpenMP correspondientes en torno al bucle principal.
-
Compila el código con el script proporcionado que generará una versión escalar y vectorial del código paralelizado:
$ ./comp.omp.sh
- Ejecuta los programas utilizando 1, 2 y 4 threads:
$ ./run.omp.sh
- A partir de los tiempos de ejecución, calcula para cada versión (escalar/vectorial) las aceleraciones (speedups) de las ejecuciones con varios threads respecto a la ejecución de este código con un thread. Calcula asimismo, la aceleración de la versión vectorial ejecutada con cuatro threads respecto la versión escalar ejecutada con un thread.
Vamos a utilizar la herramienta valgrind para obtener una estimación de varias métricas de ejecución, como por ejemplo, el número de instrucciones ejecutadas, el número fallos de predicción de salto o la tasa de fallos de cache.
-
Reducir el número total de operaciones (FLOPs) que se van a ejecutar. Para ello hay que cambiar el valor de la constante FLOP_COUNT en el fichero triad.c.
-
Recompilar el código para actualizar los binarios escalar y vectorial:
$ ./comp.omp.sh
-
Analizar y comprender el contenido del fichero run.valgrind.sh.
-
Ejecutar las herramientas callgrind y cachegrind con 1 thread:
$ ./run.valgrind.sh
-
A partir de los tiempos de ejecución, estima la sobrecarga que introducen ambas herramientas.
-
Observa los ficheros generados por callgrind y callgrind_annotate.
-
Cuál es la reducción en el número de instrucciones ejecutadas (dynamic instruction count) por la función triad en la versión avx respecto la versión noavx? Ayuda: ejecuta la siguiente orden en el directorio valgrind:
$ grep refs callg.outfile.triad.*
-
Optativo. Observa los ficheros generados por cachegrind y cachegrind_annotate.
-
Optativo. Compara las tasas de fallos de la cache de datos de primer nivel (D1) de las versiones avx y noavx. Ayuda: ejecuta la siguiente orden en el directorio valgrind:
$ grep "D1 miss rate" cacheg.outfile.triad.*
¿A qué se debe dicha diferencia?
Vamos a realizar un análisis de la ejecución del código con la herramienta Intel Application Performance Snapshot (APS): https://software.intel.com/sites/products/snapshots/application-snapshot/
Según el manual de APS: Application Performance Snapshot analyzes your application’s CPU and FPU usage, I/O and memory footprint, memory access stalls, and MPI and OpenMP* utilization. After analysis, it displays basic performance enhancement opportunities for systems using Intel® platforms. Use this tool as a first step in application performance analysis to get a simple snapshot of key optimization areas.
- Restaura el número total de operaciones (FLOPs) original. Para ello hay que cambiar el valor de la constante FLOP_COUNT en el fichero triad.c.
- Recompilar el código para actualizar los binarios escalar y vectorial:
$ ./comp.omp.sh
- Ejecuta APS para realizar el análisis de la ejecución:
$ ./run.aps.omp.sh
Analiza los informes generados (ficheros de texto y html):
- ¿Qué ocurre con el uso de CPU al aumentar el número de threads?
- ¿Qué versión tiene un CPI mayor, escalar con un thread o vectorial con un thread?