eficiencia en algoritmos paralelos - bris
TRANSCRIPT
PROGRAMACIO ́N II CÓMO MEDIR LA EFICIENCIA EN ALGORITMOS PARALELOS
Escuela Politécnica Superior de Linares
ALUMNO: Francisco Javier Bris Peñalver
2º de Ingeniería Técnica de Telecomunicación
PROFESORES: D. Francisco Charte Ojeda (T)
D. José Ramón Cano de Amo (P)
CORREO: [email protected]
FECHA: 28 de mayo de 2011
2
3
Índice
I. Definición y fundamentación del problema 4 1. Introducción. Fundamentación. 4 2. Búsqueda de nuevos métodos de análisis. 5
II. Algoritmos paralelos 6 1. Definición. 6 2. Paralelización, división, granularidad. 6 3. Punto de saturación. Ley de Amdahl. 7 4. La importancia del coste. Tipo de memoria. 8 5. Ventajas de la computación paralela. 9
III. Medida de la eficiencia 10 1. Tiempo de ejecución de un programa paralelo. 10 2. Reducción de prestaciones. Límites. 11 3. Ganancia de velocidad. Speed-up. 11 4. Medida de la eficiencia. 12 5. Coste y distribución del trabajo. 13 6. Función de Isoeficiencia. 13 7. Aplicación práctica de conceptos. 14
IV. Bibliografía 17
4
I . I . D e f i n i c i ó n y f u n d a m e n t a c i ó n d e l p r o b l e m aD e f i n i c i ó n y f u n d a m e n t a c i ó n d e l p r o b l e m a
La problemática que se trata en este trabajo es cómo podemos medir la eficiencia en algoritmos paralelos. Para ello comenzaremos definiendo el problema, argumentando por qué los métodos ‘tradicionales’ de análisis no son válidos y dando las primeras pinceladas a las posibles soluciones. Posteriormente, en el punto II explicaremos el concepto de algoritmo paralelo y trataremos de exponer de la forma más concisa posible su funcionamiento y características principales. Para terminar, el punto III aborda la solución del problema aplicando distintas reglas y conceptos.
De forma general, la medida de la eficiencia de algoritmos nos da una información precisa sobre la cantidad de recursos computacionales que va a consumir la ejecución de dicho algoritmo, y a la hora de elegir entre distintos algoritmos permite que nos decantemos por el que mejor se ajusta a nuestras necesidades o requerimientos.
De forma empírica, este consumo de recursos se medirá fundamentalmente en tiempo. Esta forma de medición conlleva una serie de problemas, como la influencia de factores externos (lenguaje de programación, máquina, compilador, carga del sistema…) y la imposibilidad de ensayo para tamaños muy grandes del problema en cierto tipo de algoritmos (algoritmos genéticos…).
Estos inconvenientes hacen necesaria la determinación matemática de su orden de eficiencia general, de una forma completamente teórica sin necesidad de tomar en cuenta factores como el compilador, el lenguaje, la máquina, etc… y genérica para cualquier tamaño del problema.
La medida de la eficiencia teórica se basa en el Principio de Invarianza: Dos medidas distintas de un mismo algoritmo no difieren en eficiencia más que en una constante multiplicativa, quedándonos con ‘el orden’ de eficiencia de dicho algoritmo (n, n·log(n), n! …). Este método general de análisis se denomina notación asintótica, y cabe destacar que es ampliamente utilizado por ser una herramienta matemática que simplifica notablemente los análisis de costes y permite expresar, de una forma concisa, los resultados. En el plano de los algoritmos paralelos, el análisis de la eficiencia de éstos suscita un gran interés ya que es más rápido tratar grandes tareas de computación mediante la paralelización que mediante técnicas secuenciales.
Es digno de mención que, en cuanto al desarrollo de microprocesadores actuales, se trabaja con la paralelización ya que se ha llegado a un punto en el que es más difícil incrementar la capacidad de procesamiento de información de un procesador “mono-núcleo” que tratar de aumentar dicha capacidad mediante la inclusión de diversas unidades de ejecución en paralelo, consiguiendo de esta manera la ejecución de varias líneas de instrucciones de forma simultanea. Esta demanda de mayor poder computacional viene respaldada por las necesidades en cuanto a modelado y simulación numérica, cálculos iterativos sobre grandes cantidades de datos y
5
algoritmos con fuertes restricciones temporales. Son sistemas cada vez más complejos que requieren mayor tiempo de cómputo.
Búsqueda de nuevos métodos de análisis.
En el caso de el análisis de eficiencia para algoritmos paralelos la complejidad aumenta. El análisis para programas secuenciales no es válido para algoritmos paralelos puesto que además de los parámetros anteriormente citados que afectan a la ejecución del algoritmo, ahora nos encontramos con nuevos factores de los que depende la eficiencia, como el número de procesadores, la interconexión entre éstos y el tipo de memoria que utilizan. En general se puede decir que el problema trata de conseguir que si el orden de eficiencia para el algoritmo secuencial era de T(n), ahora, para el algoritmo paralelizado, se reduzca a T(n)/p, donde “p” será el número de procesadores usados por el algoritmo. Con esto tratamos de acelerar la resolución del problema.
6
I I . A l g o r i t m o s p a r a l e l o sI I . A l g o r i t m o s p a r a l e l o s
Definición.
En informática, cuando hablamos de algoritmo paralelo nos estamos refiriendo a un algoritmo que puede ser ejecutado por partes, o de un modo más formal, el tipo de algoritmo en el que programa, procesador y puertos de comunicación interaccionan intercambiando tareas a través de canales para la resolución de un problema.
Paralelización, división, granularidad.
Dada la definición, instintivamente nos preguntamos ¿Hasta qué punto podemos dividir la tarea en otras pequeñas tareas paralelizadas? Para contestar a esta pregunta, tenemos que comprender el concepto de granularidad.
La granularidad de un sistema, entendiendo sistema como la conjunción de software (nuestro algoritmo) con hardware (nuestro equipo multiprocesador), indica la cantidad de computación y datos asignados a cada procesador. La granularidad está determinada por el número y el tamaño de las tareas en el cual un problema es descompuesto, pudiendo distinguir así entre grano fino y grano grueso.
Grano fino corresponde a una asignación de pocos datos por procesador o a una baja computación entre comunicaciones. Grano grueso, por el contrario, es la granularidad que se da cuando existe una gran carga de datos por cada procesador o bien se realiza mucha computación entre comunicaciones. En cuanto a algoritmos, solo nos interesará la programación paralela cuando el sistema sea de grano grueso, ya que es preferible una cantidad de datos a procesar aceptable con pocos hilos de proceso que numerosos procesadores con escasa cantidad de datos a procesar, con la información muy dividida.
Algunos algoritmos son más fáciles de dividir en partes que otros, así podemos poner como ejemplo de un problema difícilmente paralelizable el problema de los tres cuerpos, que consiste en determinar, a partir de las posiciones y velocidades de éstos los resultados de la interacción de tres cuerpos en un campo de fuerzas aislado. Este tipo de problemas se conoce como problemas inherentemente secuenciales. Otros problemas sin embargo son fácilmente divisibles en subprocesos, como el algoritmo ray tracing, propuesto por primera vez en 1968, por Apple y que actualmente es una de las simulaciones más completas en cuanto a modelos de iluminación y reflexión por computadora.
En la figura 2.1 podemos ver un sencillo gráfico donde se muestra la mejora teórica de un algoritmo paralelizado con p procesadores.
7
figura 2.1
Punto de saturación. Ley de Amdahl.
Debemos tener cuidado con la granularidad de nuestro algoritmo. No podemos olvidar que por mucho que lo dividamos, siempre se va a mantener una parte secuencial por lo que la división en un excesivo número de subprogramas puede llevar a un punto de saturación, en el que el uso de más procesadores no induce mejora alguna.
Para averiguar la mejora máxima del sistema cuando solo una de las divisiones de éste es mejorada se utiliza la ley de Amdahl:
<<La mejora obtenida en el rendimiento de un sistema debido a la alteración de uno de sus componentes está limitada por la fracción de tiempo que se utiliza dicho componente>>
Matemáticamente:
€
Tejecución mejorado = Tejecución antiguo ⋅ (1−Tusomejorado( ) +Tuso mejoradof mejora
con:
€
Gananciavelocidad =Tejecución antiguoTejecución mejorado
Introducción a la Computación Paralela 43
Aceleración según la ley de Amdahl
!"#$%&%&'()'*$+,-%./,&'0*-*$)$,&
Serial section Parallelizable sections(a) One processor
(b) Multipleprocessors
fts (1 - f)tsts
(1 - f)ts /ptp
p processors
8
O trasladado a otras palabras, el incremento de velocidad de una tarea utilizando varias unidades de proceso en computación distribuida (concepto que introduciremos más adelante) está limitado por la mínima fracción secuencial de la tarea, momento este en el que se llega al punto de saturación y no se puede paralelizar más el algoritmo.
Esta ley nos indica que dada una tarea con T divisiones independientes, tendrán mayores índices de mejora aquellas partes que contenga más datos, que necesiten más tiempo de ejecución, de ahí el principio:
“Siempre se debe optimizar el caso más común”
La importancia del coste. Tipo de memoria.
El coste representa el trabajo realizado por todo el sistema para la resolución del problema. Como hemos visto, los algoritmos paralelos no solo tienen un coste en términos del espacio (memoria) y tiempo (ciclos de procesador), sino que también necesitan optimizar la comunicación entre diferentes unidades de procesamiento. Esto se consigue mediante la aplicación de dos técnicas ampliamente extendidas, memoria compartida o paso de mensajes.
En memoria compartida nos encontramos con un único espacio de memoria. Todos los procesadores tienen acceso a la memoria a través de una red de conexión que puede ser un bus, una red de barras cruzadas o una red multietapa.
• Redes basadas en buses: Es simple y con buena capacidad de acceso a memoria. En contraposición, la cantidad de datos a transmitir limitada por el bus, y el rendimiento se satura para un número de procesadores bajo. Esto último se puede solventar mediante memorias locales de caché en los procesadores (ver figura 2.2)
• Redes de barras cruzadas: Conecta un número determinado de procesadores con una serie de módulos de memoria
figura 2.2
mediante el uso de una red de conmutadores. • Conforme crece el número de procesadores la complejidad de la red
aumenta cuadráticamente por lo que no son muy escalables. • Redes de interconexión multietapa: Es un tipo de red intermedia en
términos de escalabilidad en costo y rendimiento
La técnica memoria compartida necesita del uso de cerrojos (latches) en los datos para impedir que un bloque de información se modifique simultáneamente por dos o más procesadores, por lo que se produce un coste extra en ciclos de procesador.
9
Con memoria distribuida el computador se convierte básicamente en una colección de procesadores secuenciales que trabajan conjuntamente, donde cada procesador tiene su propia memoria local (ver figura 2.3). Se utiliza el paso de mensajes para intercambiar datos entre procesadores (acceso a memoria de otro procesador), normalmente mediante redes o sistemas de interconexión de alta
figura 2.3
velocidad. Independientemente de esta red, cada procesador tiene acceso rápido a su propia memoria. Los datos son intercambiados entre los nodos como mensajes a través de la red, por lo que las redes de ordenadores cobran especial interés para la resolución de problemas con este tipo de algoritmos paralelos. La técnica paso de mensajes añade un coste al bus, que se traduce en
memoria adicional para las colas y los mensajes. A la hora de diseñar un procesador paralelo conviene usar canales especiales para que el coste de la comunicación sea lo menor posible.
Ventajas de la computación paralela.
La principal ventaja de la programación paralela es que permite proveer al sistema de la potencia computacional necesaria para tratar y resolver problemas de gran envergadura y/o reducir los tiempos de ejecución para acelerar la obtención de soluciones.
De una forma esquemática podríamos reducirlo en:
• Resolver problemas que no caben en una CPU • Resolver problemas que no se resuelven en un tiempo razonable • Se pueden ejecutar problemas mayores • Resolver problemas más rápidamente (con mayor aceleración)
10
I II I I . M e d i d a d e l a e f i c i e n c i aI . M e d i d a d e l a e f i c i e n c i a
Tiempo de ejecución de un programa paralelo.
En el caso de un programa secuencial, el tiempo de ejecución es el tiempo que transcurre desde que se inicia la ejecución hasta que finaliza. En cambio, en el caso de un programa paralelo es el tiempo transcurrido desde que comienza la ejecución por parte del primer procesador (primer hilo que se lanza) hasta el momento en que el último procesador finaliza su ejecución.
No siempre va a ser fácil determinar el orden en que los procesadores empiezan y acaban, ya que hemos de tener en cuenta la existencia de una serie de puntos de sincronización a los que llegan los distintos procesos en un orden indefinido. Por ellos, estos puntos de sincronización en memoria tienen una duración que no siempre podemos determinar.
A priori el tiempo de ejecución vendrá determinado por:
€
t(n, p) = ta (n, p) + tc (n, p) Siendo p el número de procesadores, n el tamaño del problema, Ta(n,p) la suma de los tiempos de ejecución de las operaciones en distintas partes de computación, también conocido como tiempo aritmético. Tc(n,p) será el tiempo de comunicación, el tiempo que tarda el sistema multiprocesador en ejecutar transferencias de datos
Existe un tiempo de sobrecarga, overhead, que puede ser debido a la sincronización de la máquina, la puesta en marcha de los procesos y a posibles retardos de la red de comunicación. También existe el tiempo de solapamiento, que se puede definir como el intervalo de tiempo en el que las operaciones aritméticas y las de comunicaciones se realizan de forma simultánea. Este tiempo, aún siendo muy importante, suele ser muy difícil de calcular.
Por tanto, el tiempo de ejecución se puede expresar de una forma más detallada:
€
t(n, p) = ta (n, p) + tc (n, p) + tov (n, p) − tsol (n, p)
A partir de esta fórmula podemos concluir que se puede llevar a cabo una buena mejora de la eficiencia del algoritmo reduciendo al mínimo el tiempo de sobrecarga y maximizando el tiempo de solapamiento.
11
Reducción de prestaciones. Límites.
• Código secuencial: Como ya hemos comentado en apartados anteriores, puede haber parte imposible de paralelizar. Un ejemplo puede ser la inicialización de variables.
• Contención de memoria: Problema que se da con el acceso a datos comunes o que están en el mismo bloque de memoria. En el caso en que los datos son de lectura, normalmente no se da ese problema.
• Tiempo de creación de procesos: Cuantos más subprocesos se quieran crear, más tarda el programa en ponerlos en marcha. El coste de creación de los procesos puede ser importante si la granularidad de éstos es pequeña.
• Comunicaciones: Como hemos mencionado, es el tiempo de transferencia de datos entre procesadores.
• Tiempo de sincronización: Se da cuando un proceso tiene que esperar a que estén disponibles datos procesados por otro.
• Desbalanceo de la carga: Se define como la carencia de una distribución equitativa del volumen de computación entre todos los procesadores. Puede darse que el volumen de datos varíe a lo largo de la ejecución, lo que haría necesario algún tipo de balanceo dinámico.
Ganancia de velocidad. Speed-up.
Fundamentalmente el Speed-‐Up es una técnica de la que se obtiene una relación de mejora entre el tiempo de ejecución de un programa secuencial (con un solo procesador) y en paralelo (con p procesadores). Viene definido por la siguiente fórmula:
Speed-Up:
€
S(n, p) =t(n)t(n, p)
A partir de esta fórmula podemos pensar aplicando una lógica sencilla que el Speed-‐Up simplemente corresponderá con el número de procesadores del algoritmo paralelo. Esta afirmación se conoce como Speed-Up ideal y está considerada como una buenísima escalabilidad del algoritmo. Sin embargo, en la práctica es bastante complicado ya que conduciría a una ganancia de velocidad lineal, donde doblando el número de procesadores se doblara la velocidad de cómputo.
12
figura 3.1
Excepcionalmente puede darse un Speed-‐Up supralineal, que consiste teóricamente en superar una ganancia de velocidad lineal del programa paralelo respecto al secuencial. Este fenómeno se suele dar a causa de una mejor gestión de memoria y por la acumulación de memoria caché de los procesadores. También es relativamente usual cuando se implementan algoritmos con backtraking en paralelo.
Medida de la eficiencia. Escalabilidad.
La eficiencia se define en un algoritmo paralelo como el cociente entre el Speed-‐Up y el número p de procesadores. Nos proporciona una idea de la porción de tiempo que los procesadores se dedican a trabajo útil, también entendido como rendimiento.
€
E =S(n, p)p
El valor máximo que puede alcanzar es uno, que corresponde con el índice de máximo aprovechamiento, 100%. Normalmente, al aumentar el número de procesadores el índice de eficiencia se aleja del valor de máximo aprovechamiento; en cambio, para un número de procesadores determinado, aumentará conforme aumente el tamaño del problema…
Cuando hablamos de que en ciertos sistemas las prestaciones se mantienen cuando aumenta tanto el número de procesadores como el tamaño del problema hablamos de la escalabilidad del sistema.
Conocer si un sistema es escalable es muy importante para saber si en un futuro, dado un hipotético aumento del tamaño del problema y de la cantidad de procesadores se mantendrá la eficiencia del programa. Si un algoritmo no es escalable, aunque se aumente el número de procesadores, no se conseguirá mantener la eficiencia aunque se aumente a la vez el tamaño del problema, con lo que cada vez se aprovechará menos la potencia de los procesadores.
0
50
100
150
200
250
300
0 32 64 96 128 160 192 224 256
Speed-‐Up ideal
Speed-‐Up real
13
Distribución del trabajo. Mapeo y equilibrado de carga.
El mapeo es el proceso por el cual decidimos asignar tareas a procesadores. El objetivo es obtener un programa que se ejecute de la forma más eficiente posible en el sistema del que disponemos.
Partimos de un conjunto de tareas o hilos que se pueden lanzar concurrentemente y un conjunto de enlaces entre ellas que representan los requerimientos de comunicación. En función de esto y de los recursos disponibles debemos diseñar una distribución de trabajo lo más equilibrada posible.
El balanceo o equilibrado de la carga es la distribución o división del trabajo de una forma equitativa entre los procesadores de nuestro sistema. Para algunos problemas puede ser necesario un proceso de equilibrado continuo de carga (equilibrado dinámico).
Una correcta distribución del trabajo:
• En memoria distribuida: Puede reducir el coste de las comunicaciones asignando datos que se comunican frecuentemente a procesadores vecinos. Evita comunicaciones y congestión de la red innecesaria.
• En memoria compartida: Puede influir en el coste de los accesos a memoria, en el caso en que procesadores distintos accedan a bloques distintos. En este caso además mejora el uso de la jerarquía de memoria.
Función de Isoeficiencia.
Función cuyo cometido es el de ofrecernos información acerca de cómo debe crecer el tamaño del problema en función del número de procesadores para poder mantener una eficiencia constante en sistemas escalables.
En general, para obtener esta función basta con comparar el tiempo secuencial con la función de overhead. Cuanto menor sea la función de Isoeficiencia, mejor, ya que el sistema será más escalable y esto conlleva una serie de beneficios como los enunciados anteriormente.
La función overhead (no confundir con el concepto de overhead) es la diferencia entre el tiempo de ejecución del algoritmo y el tiempo de ejecución del algoritmo idealizado, es muy difícil de medir.
14
Aplicación práctica de conceptos.
A partir de los conceptos previamente expuestos vamos a proceder a comentar algunos aspectos de un algoritmo: “búsqueda de un archivo de manera paralelizada”, implementado por José Carlos Rodríguez.
import java.io.File; public class Busqueda extends Thread { String nombreBuscar; File[] entradas; public Busqueda(String nombreBuscar, File ruta) { entradas = ruta.listFiles(); this.nombreBuscar=nombreBuscar; } @Override public void run() { String nombreArchivo; String nombre[]; for (File entrada : entradas) { if(entrada.isDirectory()) { new Busqueda(nombreBuscar, entrada).start(); } if(entrada.isFile()) { nombre=entrada.getName().split("[.]"); nombreArchivo=nombre[0]; if(nombreArchivo.equals(nombreBuscar)) { System.out.println("Archivo Encontrado en:\n\n"); System.out.println(entrada+"\n\n"); } } } }
Declaraciones previas… Para cada una de las entradas Comprueba si la entrada es una carpeta. En caso afirmativo inicia un nuevo proceso de búsqueda. Comprueba si entrada es un fichero. En caso afirmativo comprueba si tiene el nombre buscado. En caso afirmativo devuelve ubicación.
El funcionamiento del algoritmo es el siguiente: para cada nivel de directorio, realiza una búsqueda comprobando cada elemento para ver si es el fichero buscado (caso en el que terminaría el programa) o es un directorio.
15
Por cada directorio que se encuentre en el nivel, se accede a éste mediante un nuevo proceso que lanza de nuevo el algoritmo de búsqueda comparando cada elemento de la lista de ficheros con el buscado.
En función de las variables del problema que podríamos determinar como número medio de ficheros por directorio y número de directorios, podemos distinguir varios casos:
Caso primero: Pocos ficheros y pocos directorios.
En este caso el uso de un algoritmo paralelo no tiene sentido ya que, como hemos visto, éstos son ventajosos para tratar y resolver problemas de gran envergadura. Para pocos ficheros y pocos directorios no se requiere una gran potencia computacional ni consumirá excesivo tiempo, al no ser un problema de envergadura.
Además, una hipotética mejora del rendimiento (mayor aceleración) inducida por el uso de computadores paralelos no sería significativa.
Caso segundo: Pocos ficheros y muchos directorios.
En este caso el uso de un algoritmo paralelo no solo no tiene sentido por los motivos expuestos en el apartado anterior, sino que además puede empeorar los tiempos debido a que, al ser un problema con una alta granularidad (poca carga de trabajo para gran número de procesos), tenemos una importante reducción de las prestaciones por la creación de procesos, el tiempo de comunicación entre procesadores y el tiempo de sincronización.
Es posible que con un algoritmo con balanceo dinámico de la carga, que reparta rápidamente las cargas de trabajo entre los distintos procesadores las prestaciones se mantuvieran, pero en cualquier caso estamos hablando de una alta complejidad del programa para mantener las prestaciones de antes de paralelizar el algoritmo.
Caso tercero: Muchos ficheros y pocos directorios
Este es el caso en el que probablemente tiene todo el sentido el uso de un programa paralelo, ya que estamos ante una muy baja granularidad (una gran carga de datos por cada procesador), lo que se traduce en una buena ganancia de velocidad al ejecutar varios hilos simultáneamente.
Además, estamos ante un caso en el que quedan bastante limitados los tiempos de comunicación entre procesadores y de creación de procesos, lo que contribuye a una buena eficiencia del algoritmo.
Cabe destacar que la ejecución de este algoritmo, por la comparación de cada elemento con el buscado secuencialmente, puede llevarnos a tiempos prohibitivos si nos movemos con un elevado número de ficheros.
16
Caso cuarto: Muchos ficheros y muchos directorios
En este caso tenemos los beneficios del caso inmediatamente anterior a este, como la baja granularidad y la buena ganancia de velocidad al ejecutar varios hilos simultáneamente, pero perdemos algunos otros beneficios ya que tenemos una bajada de prestaciones debida a los tiempos de comunicación entre procesadores y de creación de procesos.
Dicho esto, en este caso también sería apropiado el uso de algoritmos paralelos ya que podemos obtener una buena ganancia en velocidad sobre todo si, aun siendo ambas altas, la cantidad de ficheros es muy elevada en comparación con el número de directorios.
Como conclusión, extensible a la gran mayoría de los programas, la paralelización de un algoritmo solo se justifica cuando exista una fuerte demanda de capacidad de cómputo, y será prohibitivo su uso cuando estemos ante algoritmos con poco consumo de recursos y/o muy subdivididos.
17
I VI V . . B i b l i o g r a f í aB i b l i o g r a f í a
Para la realización de este trabajo me he basado en numerosas fuentes:
• Apuntes de la asignatura: “Computación de altas prestaciones” (cuyas referecias
son: Foster, cap 3; Kumar, Grama, Gupta, Karypis, cap 4; Wilkinson, Allen, cap
2.3 y 2.4)
• Información obtenida de diversos artículos de Wikipedia:
o Algoritmo paralelo
o Computación paralela
o Hilo de ejecución
• Apuntes variados del Departamento de Ciencias de la Computación de la
Universidad de Chile.
• Transparencias de “Arquitectura e Ingeniería de Computadores II” (Profersor: D.
Juan Antonio Maestro, de la Universidad Antonio de Nebrija)
• Transparencias de la asignatura “Algoritmos Paralelos” Tema 1. Introducción a la
computación paralela. (Profesor: D. Vicente Cerverón, de la Universidad de
Valencia)
Cabe destacar también que me ha sido de ayuda poder contrastar información con
algunos puntos de la Tesis Doctoral sobre Algoritmos Paralelos de D. Rafael Arturo
Trujillo Rasúa, de la Universidad Politécnica de Valencia.