ARQUITECTURA DE SISTEMAS PARALELOS 2. 4º INGENIERÍA INFORMÁTICA. PRÁCTICA 4. PROGRAMACION PARALELA CON openMP. 1. OBJETIVOS Y PREPARACIÓN. En la actualidad los multiprocesadores en un solo chip (denominados habitualmente procesadores de múltiple núcleo) están extendiéndose de manera muy notable. Si queremos aprovechar las posibilidades de estos procesadores es necesario particionar nuestro código en diversos hilos de forma que puedan ejecutarse simultáneamente tanto hilos como procesadores tengamos. Con el fin de facilitar este proceso se han introducido herramientas que permiten realizar esta partición de manera sencilla sin tenerse que preocupar de crear y destruir los hilos o de gestionar las comunicaciones entre ellos de forma explicita. Dentro de estas herramientas una de las más populares es el entorno openMP que hoy en día está soportado en la mayoría de multiprocesadores con memoria compartida. En la familia 80x86 openMP está soportado los compiladores de C++ de Microsoft e Intel (Windows y Linux) y otros. La referencia de openMP podemos encontrarla en http://www.openmp.org/drupal/node/view/8 y un buen tutorial en español en http://www.arcos.inf.uc3m.es/~ii_ac2/04-05/OpenMP_curso.pdf OpenMP utiliza para generar el código paralelo directivas y funciones insertadas en el código C++ (o Fortran). En C la directivas usan el formato: #pragma omp directiva [comando[(parámetros)], [comando[(parámetros)]… Tanto las directivas como las funciones no son tenidas en cuenta si no habilitamos la compatibilidad openMP en el compilador. En Visual Studio la opción para habilitar está compatibilidad está, dentro de las propiedades del proyecto en: Propiedades de Configuración -> C/C++ -> Lenguaje -> Compatibilidad con openMP. La práctica estudiará la optimización del código SAXPY ya empleado en la práctica 3 en un multiprocesador. Para completarlo introduciremos un nuevo ejemplo con dependencias a través del bucle: el producto escalar. Para realizar estos ejemplos sólo necesitamos las directivas de openMP referentes a la optimización de bucles for . Estas son (ver tutorial): • #pragma omp parallel • #pragma omp for • Cláusula combinada que une las dos anteriores: #pragma omp parallel for Dentro de esta directiva se puede especificar (ver tutorial) • Variables locales a cada thread (private()) y compartidas entre todos (shared()). Por defecto la variable índice del bucle es privada y las demás son compartidas. • Políticas de partición de carga (static o dynamic) • Posibilidad de operadores de reducción. 2. REALIZACIÓN DE LA PRÁCTICA. 2.1. PRUEBA 1: Comparación Inicial. 1. Obtener las características del procesador empleado usando cpuz 1.40 (descargarlo de http://www.cpuid.com/ ) 2. Abrir el entorno Visual Studio .NET y crear un proyecto de consola WIN32 vacío. Añadir un archivo de utilidad TXT y nombrarlo con extensión “.c”. Copiar el código fuente que se proporciona en el enunciado de esta práctica. Poner el compilador en modo release y no habilitar openMP. Apuntar los tiempos en la tabla de resultados. 2.2. PRUEBA 2: OpenMP básico. 1. Añadir las directivas para paralelizar los bucles del SAXPY y del producto escalar. Probar con un número de threads entre 1 y el número de núcleos (o hyperthreads) del procesador. Anotar resultados. 2.3. PRUEBA 3: Políticas de secuenciamiento. 1. Probar políticas de secuenciamiento estáticas y dinámicas con al menos 2 tamaños de partición de datos (chunk). Anotar resultados. Interpretación de Resultados: Realizar una interpretación cualitativa de los resultados obtenidos en las diversas pruebas en función de las características del procesador y: • Habilitación o no de openMP. • Número de Threads. • Tamaño de los vectores. • Políticas de secuenciamiento y partición. Arquitectura de Sistemas Paralelos 2. Dpto. ATC. Práctica 4. Explicar las diferencias entre los tiempos del SAXPY y los del producto escalar. ARQUITECTURA DE SISTEMAS PARALELOS II. PRACTICA 4 ANÁLISIS DE INSTRUCCIONES DEL NÚCLEO MULTIMEDIA. NUM GRUPO: ALUMNOS: Características del Ordenador. Procesador (Modelo /Frecuencia/Núcleos/HT) Cache(s) de nivel 1º. Cache de Nivel 2º Memoria Principal Chipset TAM Tabla de resultados SAXPY básico: omp 1 thread Sin omp tiempo aceleración L1/2 elem 1.0 L1 elem 1.0 2*L1 elem 1.0 L2/4 elem 1.0 L2/2 elem 1.0 L2 elem 1.0 2*L2 elem 1.0 TAM Tabla de resultados PE básico: Sin omp tiempo aceleración L1/2 elem 1.0 L1 elem 1.0 2*L1 elem 1.0 L2/4 elem 1.0 L2/2 elem 1.0 L2 elem 1.0 2*L2 elem 1.0 tiempo Omp__ threads aceleración omp 1 thread tiempo tiempo aceleración Omp__ threads aceleración tiempo aceleración Arquitectura de Sistemas Paralelos 2. Dpto. ATC. Práctica 4. Tabla de resultados SAXPY (políticas secuenciamiento. Indicar Política y tamaño partición): TAM tiempo aceleración tiempo aceleración tiempo aceleración tiempo aceleración tiempo aceleración L1/2 elem L1 elem 2*L1 elem L2/4 elem L2/2 elem L2 elem 2*L2 elem Tabla de resultados PE (políticas secuenciamiento. Indicar Política y tamaño partición): TAM tiempo aceleración tiempo aceleración tiempo aceleración L1/2 elem L1 elem 2*L1 elem L2/4 elem L2/2 elem L2 elem 2*L2 elem Justificación de resultados: Arquitectura de Sistemas Paralelos 2. Dpto. ATC. Práctica 4. ESQUELETO DEL CÓDIGO DE PRUEBA #include <stdio.h> #include <math.h> #include <omp.h> #define #define #define #define #define MHZ 3E3 VSIZE (512) REPETICIONES (1024) N 256 TAM VSIZE*N __declspec(align(16)) float x[TAM], y[TAM], z[TAM], m[4] = {1.0, 2.0, 3.0, 4.0}; // alinea en frontera de 16 bytes los vectores. float comp[TAM]; //vector para comprobar resultados float a = (float) 3.14159; float b = 0.5,pe; int ciclos[2]; void codigo_c (int tam); float codigo_cpe (int tam); //inicialización con instr. SSE: x[] = {1, 2, 3, 4, 1, 2, 3, 4,.. }, y[i] = sqrt(x[i]) void inic (int tam); int main(void) { int i; //omp_set_num_threads(2); //Fijar numero de Threads for (i=1; i<=N; i*=2) { printf("\n::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); printf("\n::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); printf("\n\nCodigo C para %d elementos\n", i*VSIZE); Arquitectura de Sistemas Paralelos 2. Dpto. ATC. Práctica 4. codigo_c (i*VSIZE); //codigo C en otra funcion para que no influyan las optimizaciones del compilador //--------------------------------------------------------------------------printf( " -> Elem primero y ultimo: %.5e , %.5e \n", y[0], y[i*VSIZE-1]); //--------------------------------------------------------------------------pe=codigo_cpe (i*VSIZE); //--------------------------------------------------------------------------printf( " -> Producto Escalar %.5e \n", pe); //--------------------------------------------------------------------------- printf("\n\n"); } return 0; } //*********************************************************************** // FUNCIONES //*********************************************************************** //codigo C en void codigo_c int i, int min = otra funcion para que no influyan las optimizaciones del compilador (int tam) { j; 0x7fffffff; inic (tam); for (j=0; j<REPETICIONES; j++) { __asm { lea esi, ciclos mfence rdtsc mov [esi], eax mov [esi+4], edx //Codigo C (del SAXPY O SIMILAR) //#pragma omp ----- AÑADIR DIRECTIVA for (i=0; i<tam; i++) y[i] = a*x[i] + y[i]; // Fin codigos de prueba __asm { mfence rdtsc lea esi, ciclos sub eax, [esi] sbb edx, [esi+4] mov [esi], eax mov [esi+4], edx mfence } if (min > ciclos[0]) min = ciclos[0]; } printf( " --> %u ciclos => %.3lf useg \n", min, min / MHZ); //Guardo resultados correctos en vector comp[] for(i=0;i<tam;i++) comp[i] = y[i]; } Arquitectura de Sistemas Paralelos 2. Dpto. ATC. Práctica 4. float codigo_cpe (int tam) { int i, j; int min = 0x7fffffff; float res; inic (tam); for (j=0; j<REPETICIONES; j++) { __asm { lea esi, ciclos mfence rdtsc mov [esi], eax mov [esi+4], edx } // Codigos de prueba //Codigo C (Producto escalar) res=0; //#pragma omp --- Añadir directiva for (i=0; i<tam; i++) res+=x[i] * y[i]; // Fin codigos de prueba __asm { mfence rdtsc lea esi, ciclos sub eax, [esi] sbb edx, [esi+4] mov [esi], eax mov [esi+4], edx mfence } if (min > ciclos[0]) min = ciclos[0]; } printf( " --> %u ciclos => %.3lf useg \n", min, min / MHZ); //Guardo resultados correctos en vector comp[] return res; } void inic (int tam) { __asm { lea eax, m lea ebx, x lea edx, y mov edi, tam shl edi, 2 mov ecx, ebx add ecx, edi movaps xmm0, [eax] sqrtps xmm1, xmm0 lp: movaps [ebx], xmm0 movaps [edx], xmm1 add ebx, 16 add edx, 16 cmp ebx, ecx jne lp } Arquitectura de Sistemas Paralelos 2. Dpto. ATC. Práctica 4.