1. Concurrencia compartida
La concurrencia de recursos compartidos, o simplemente concurrencia compartida ocurre cuando varios trabajadores (hilos de ejecución) realizan una tarea concurrentemente y pueden acceder a regiones compartidas de memoria. Los hilos de ejecución siguen este esquema, dado que comparten todos los segmentos del proceso (código, datos, y memoria dinámica), excepto el segmento de pila. Por ejemplo, un dato que un hilo escriba en la memoria compartida, otro hilo puede leerlo inmediatamente, sin hacer ninguna preparación especial. De esta forma, la memoria compartida se convierte en un mecanismo directo de comunicación entre los hilos de ejecución.
Se estudiarán en el curso tres tecnologías para implementar concurrencia compartida: Pthreads, OpenMP, y OpenACC. Éste último es de carácter opcional.
1.1. Concurrencia compartida imperativa (Phtreads)
Los POSIX Threads es un estándar que permite a los desarrolladores crear y controlar hilos de ejecución en los sistemas operativos basados en Unix. Cree en una carpeta ejemplos/pthreads
para los siguientes ejemplos en su repositorio personal de control de versiones para el curso. Para cada ejemplo cree una subcarpeta que utilice el nombre dado entre corchetes.
1.1.1. Hola mundo (thread)
El siguiente es un "Hola mundo" en Pthreads:
Archivo | Descripción | Cambios |
---|---|---|
Dice "hola mundo" desde el hilo principal y desde un hilo secundario. |
Para compilar con GCC –o un compilador compatible– código fuente que use Pthreads, se debe agregar el parámetro -pthread
:
cc -g -Wall -Wextra hello.c -o hello -pthread
echo hello > .gitignore
git add hello.c .gitignore
Recuerde siempre ignorar los ejecutables en su repositorio de control de versiones. Para ello, cree o agregue a su archivo .gitignore
el nombre del ejecutable. Luego agregue el código fuente y el .gitignore
a control de versiones. Los dos últimos comandos de arriba hacen este trabajo.
1.1.2. Hola mundo múltiple (indeterminismo)
En la siguiente solución se incluye además un archivo de reglas Makefile
que permite a un desarrollador compilar el código fuente sin tener que recordar los argumentos con los que se debe invocar al compilador. Bastará que el programador emita el comando make
en la carpeta, y éste invocará al compilador siguiendo la única regla estipulada en el Makefile
de abajo.
Archivo | Descripción | Cambios |
---|---|---|
Varios threads dicen "hola mundo" junto con el hilo principal. |
||
Un archivo para compilar el código fuente sin tener que recordar los argumentos de invocación al compilador. |
1.1.3. Hola mundo numerado (memoria privada)
Archivo | Descripción | Cambios |
---|---|---|
Varios threads saludan indicando su identificador en la salida estándar. |
||
El Makefile cambia usa una variable para especificar el nombre del ejecutable y del archivo fuente que facilita modificaciones. |
1.1.4. Hola mundo numerado (memoria compartida)
Archivo | Descripción | Cambios |
---|---|---|
Varios threads saludan indicando su identificador en la salida estándar. |
||
Cambia el nombre de la carpeta usando la variable. |
1.1.5. Hola mundo ordenado (espera activa)
La espera activa es un ciclo que hace a un hilo de ejecución esperar repetitivamente hasta que una condición se haga falsa. Por ejemplo, si la variable next_thread
indica el número del próximo thread que puede realizar una tarea que debe ser serializada en orden, el código
// Wait until it is my turn
while ( next_thread < my_thread_id )
; // busy-wait
// Do the ordered-task here
task();
// Allow subsequent thread to do the task
++next_thread;
Archivo | Descripción | Cambios |
---|---|---|
Varios threads saludan en orden gracias a la espera activa. |
||
Obtiene automáticamente el nombre del ejecutable de la carpeta donde se encuentre el |
1.1.6. Posición en la carrera (mutex)
Archivo | Descripción | Cambios |
---|---|---|
Varios threads compiten en una carrera, y reportan el orden en que llegan a la meta. Utilizan exclusión mutua cuando llegan a la meta para incrementar la variable contadora correctamente y para evitar la condición de carrera en la impresión en la salida estándar. |
||
Sin cambios respecto a la versión anterior |
1.1.7. Productor-consumidor (semáforo)
Archivo | Descripción | Cambios |
---|---|---|
Simula el problema del productor-consumidor. |
||
Usa variables para el compilador, las banderas, y bibliotecas. Estas variables pueden ser cambiadas desde la línea de comandos cuando se invoca a |
1.1.8. Carrera de relevos (barrera)
Archivo | Descripción | Cambios |
---|---|---|
Simula una carrera de relevos con threads. Esta solución se compara contra el ejemplo de posición en la carrera ( |
||
Sin cambios |
1.1.9. Misterio (variable de condición)
1.1.10. Arreglo reentrante y thread-safe (candado de lectura-escritura)
Archivo | Descripción | Cambios |
---|---|---|
Respecto al código dado, se cambió |
||
El registro que contiene los atributos (campos) de un arreglo se hace opaco (sus campos son privados para los usuarios, por lo que no se declaran en la interfaz |
||
Las variables estáticas/globales se reemplazan por campos en registros. La función |
||
Un proyecto de Qt para quien guste usar este IDE. |
||
Este |
Archivo | Descripción | Cambios |
---|---|---|
Programa dado que prueba que crea varios threads y estos realizan operaciones concurrentemente sobre el arreglo. |
||
Dado que el registro es opaco, la interfaz del arreglo se mantiene inalterada. Sólo se cambio el orden de declaración de las subrutinas. |
||
Agrega un mutex al registro y hace que las funciones públicas se invoquen con exclusión mutua en el arreglo. |
||
Un proyecto de QtCreator para quien guste usar este IDE. |
||
Idéntico al anterior. |
Archivo | Descripción | Cambios |
---|---|---|
Programa dado que prueba idéntico. |
||
Dado que el registro es opaco, la interfaz del arreglo se mantiene inalterada. |
||
Cambia el mutex por un candado de lectura y escritura. Las funciones públicas que modifican el arreglo bloquean el candado para escritura, y las que sólo leen del arreglo lo bloquean para lectura. |
||
Un proyecto de QtCreator para quien guste usar este IDE. |
||
Idéntico al anterior. |
Archivo | Descripción | Cambios |
---|---|---|
Programa dado que prueba que crea un arreglo de acuerdo a los argumentos del usuario, varios threads y estos realizan operaciones concurrentemente sobre el arreglo. |
||
Se renombraron los identificadores para evitar colisión. |
||
Se renombraron los identificadores para evitar colisión. |
||
Se renombraron los identificadores para evitar colisión. |
||
Se renombraron los identificadores para evitar colisión. |
||
Un proyecto de QtCreator para quien guste usar este IDE. |
||
Idéntico al anterior. |
||
Hoja de cálculo con los resultados de ejeción del experimento en una computadora con 8 núcleos lógicos. |
1.2. Problemas de sincronización
Los dos tipos de problemas que resuelve el paradigma de programación concurrente son los que requieren incremento de desempeño y separación de asuntos.
-
El incremento del desempeño se busca principalmente al optimizar algorimos que requieren mucho poder de cómputo o procesar grandes volúmenes de datos. Son de especial interés para otras disciplinas que requieren apoyo de la computación. Se busca principalmente maximizar el paralelismo de datos a través de entes de ejecución (hilos o procesos) y disminuir la cantidad de comunicación entre los entes.
-
La separación de asuntos no busca tanto la optimización, sino que los entes realicen tareas distintas de forma correcta. Los problemas en esta categoría son de especial interés para la computación misma, dado que son aplicables a variedad de herramientas como sistemas operativos, motores de bases de datos, servidores web, entre otros. Típicamente los problemas en esta categoría buscan que el paralelismo de tareas genere resultados correctos a través de la aplicación de mecanismos de sincronización.
Esta sección se concentra en problemas del segundo tipo de la lista anterior, muchos de los cuales tienen aspecto de acertijos y resultan muy interesantes para el profesional de la computación. Están basados en el libro libre The Little Book of Semaphores escrito por Allen B. Downey.
1.2.1. Rutas de ejecución
Archivo | Descripción | Cambios |
---|---|---|
Lista todas las posibles rutas de ejecución. |
Archivo | Descripción | Cambios |
---|---|---|
Conjetura los potenciales resultados. |
1.2.2. Definición original de semáforo
La definición original de semáforo por Dijkstra puede variar ligeramente de algunas implementaciones. Para Dijkstra un semáforo es un entero con signo, con tres características:
-
Cuando se crea un semáforo, éste se inicializa con un entero cualquiera (negativo, cero, o positivo). Pero después de inicializado las únicas dos operaciones que están permitidas es incrementar en uno (signal) y decrementar en uno (wait) al semáforo. No se puede leer el valor actual del semáforo.
-
Cuando un hilo decrementa un semáforo, si el resultado es negativo, el hilo es bloqueado y no puede continuar hasta que otro hilo incremente el semáforo.
-
Cuando un hilo incrementa un semáforo, si hay otros threads esperando, uno de ellos será desbloqueado. Tanto el hilo que incrementa el semáforo como el que fue desbloqueado siguen ejecutándose concurrentemente. Si hay varios hilos esperando, no hay forma de saber cuál de ellos será el desbloqueado por el scheduler del sistema operativo. El programador no tiene forma de saber si al incrementar un semáforo, se desbloqueará o no un hilo en espera, dado que no se puede leer el valor actual del semáforo por la regla 1.
El valor actual de un semáforo indica lo siguiente. Si el valor es positivo indica la cantidad de hilos que pueden decrementar el semáforo sin bloquearse. Si es negativo indica la cantidad de hilos que están bloqueados esperando actualmente por el semáforo. Si el valor es cero, indica que no hay hilos esperando por el semáforo, pero si un thread trata de decrementarlo, será bloqueado.
Algunas implementaciones, por ejemplo POSIX, permiten probar la espera (sem_trywait). Esta función nunca bloquea al hilo que la invoca. Si se trata de esperar un semáforo que tiene un valor positivo, se decrementará el semáforo y el hilo continuará ejecutándose como una invocación normal a wait()
. Pero si se trata de esperar por un semáforo que quedaría en un valor negativo, la función sem_trywait()
no decrementa al semáforo ni bloquea al hilo, sino que retorna de inmediato indicando un código de error (por ejemplo, -1
). Dado que el hilo mantiene su ejecución, el programador puede decidir, condicionando (if
) el valor de retorno del sem_trywait()
y tomar distintas acciones que realice el hilo cuando se pudo o no esperar por el semáforo.
En los siguientes ejemplos se seguirá la definición original de Dijkstra, que no permite probar la espera. Se usará pseudocódigo con la siguiente sintaxis para los hilos y semáforos, con el fin de centrar la atención en estos temas y no en detalles de implementación de una tecnología particular (ej.: Pthreads):
sem := semaphore(3) // Create a semaphore with initial value 3
signal(sem) // Increment and wake a waiting thread if any
wait(sem) // Decrement and block if the result is negative
// Declares a shared variable by all threads (ie. stored in shared_data record)
shared shared_x := initial_value
// Main subroutine that will be executed by the main thread
main:
// Create 3 threads that will execute the secondary function concurrently
create_threads(secondary, 3)
secondary:
// A subroutine that will be executed by secondary threads
1.2.3. Avisar/notificar (signal)
Archivo | Descripción | Cambios |
---|---|---|
Usa un semáforo que indica cuando la instrucción |
1.2.4. Encuentro (rendezvous)
Archivo | Descripción | Cambios |
---|---|---|
Usa dos semáforos para asegurar que dos threads han llegado a cierto punto. En esta versión, los dos threads avisan apenas hayan terminado de ejecutar las instrucciones |
||
En esta versión un thread primero espera y el otro avisa. Es una solución correcta aunque ligeramente menos eficiente que la versión 1. |
||
En esta versión ambos threads esperan a que el otro haya terminado de ejecutar su instrucción. No es una solución porque genera un bloqueo mutuo (deadlock). |
1.2.5. Exclusión mutua con semáforos (mutex)
Archivo | Descripción | Cambios |
---|---|---|
Un semáforo inicializado en 1 imita a un mutex. Cuando el semáforo tiene el valor 1 indica que el mutex está disponible, y el valor 0 que está bloqueado. Tiene la diferencia de que un mutex no debe superar el valor 1, y de que un mutex sólo puede ser incrementado por el mismo thread que lo decrementó. |
Archivo | Descripción | Cambios |
---|---|---|
El semáforo usado adecuadamente obliga a una serialización de los hilos. |
1.2.6. Exclusión mutua acotada (multiplex)
Archivo | Descripción | Cambios |
---|---|---|
Simplemente se inicializa el semáforo con el límite superior de threads permitidos concurrentemente. Si el semáforo llega a alcanzar el valor 0, indica que se agotó la capacidad concurrente y los hilos subsecuentes tendrán que esperar. Cuando un hilo incrementa el semáforo es porque "sale del salón" y deja espacio para que otro ingrese. ¿Cuál será el valor final del semáforo cuando todos los hilos hayan pasado por la sección crítica? |
1.2.7. Barrera con semáforos (barrier)
Archivo | Descripción | Cambios |
---|---|---|
Implementa una barrera de una pasada. Es decir, después de usada, la barrera no se debe reutilizar. |
Archivo | Descripción | Cambios |
---|---|---|
Usa dos torniquetes (trompos) para evitar que hilos de ejecución ávidos salgan de la barrera y vuelvan a ingresar a ella antes que otros hayan salido. |
||
Pseudocódigo que convierte la barrera en código reusable. |
La segunda implementación permite reutilizar barreras. Es decir en la solución de nuevos problemas puede suponer que tiene disponible barreras con la siguiente interfaz:
1
2
3
4
5
6
7
main:
shared my_barrier := barrier(3)
secondary:
...
wait(mybarrier)
...
1.2.8. Parejas de baile (colas con semáforos)
Archivo | Descripción | Cambios |
---|---|---|
En el fondo esta solución es simplemente un encuentro (rendezvous). Cuando una persona llega a su fila A, enviará una señal a la otra fila B indicando que está listo para formar pareja. Si la fila B está vacía, la persona se quedará esperando hasta que alguien llegue a la fila B y le envíe una señal. Sin embargo, puede ocurrir que varios hombres bailen sin pareja o entre ellos, o viceversa. Busque una ruta de ejecución que produzca este comportamiento. |
||
La versión anterior fue realizada en clase, donde un valor negativo en la cola de hombres significa la cantidad de mujeres esperando a que llegue un hombre (y viceversa). Esta es otra versión simétrica donde un valor negativo en la cola de hombres indica la cantidad de hombres esperando por ella (y viceversa). |
Archivo | Descripción | Cambios |
---|---|---|
Solución propuesta por Julián y Roy. |
||
Solución propuesta por Kevin Wang Qiu |
||
Solución propuesta por Roy Rojas. |
1.2.9. Filósofos comensales
(Pendiente)
1.2.10. Fumadores de cigarrillos
Originalmente presentado por Suhas Patil. Four threads are involved: an agent and three smokers. The smokers loop forever, first waiting for ingredients, then making and smoking cigarettes. The ingredients are tobacco, paper, and matches.
We assume that the agent has an infinite supply of all three ingredients, and each smoker has an infinite supply of one of the ingredients; that is, one smoker has matches, another has paper, and the third has tobacco.
The agent repeatedly chooses two different ingredients at random and makes them available to the smokers. Depending on which ingredients are chosen, the smoker with the complementary ingredient should pick up both resources and proceed. For example, if the agent puts out tobacco and paper, the smoker with the matches should pick up both ingredients, make a cigarette, and then signal the agent.
To explain the premise, the agent represents an operating system that allocates resources, and the smokers represent applications that need resources. The problem is to make sure that if resources are available that would allow one more applications to proceed, those applications should be woken up. Conversely, we want to avoid waking an application if it cannot proceed.
You are not allowed to modify the agent code. If the agent represents an operating system, it makes sense to assume that you don’t want to modify it every time a new application comes along.
El siguiente pseudocódigo provee la implementación del agente, el cual delega trabajo en sub-agentes que proveen los materiales. Se provee una implementación intuitiva para cada fumador. ¿Qué problema presenta esta solución?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
main:
shared agent_sem := semaphore(1)
shared match := semaphore(0)
shared paper := semaphore(0)
shared tobacco := semaphore(0)
create_thread(agent)
create_thread(smoker_with_matches)
create_thread(smoker_with_paper)
create_thread(smoker_with_tobacco)
agent:
create_thread(agent_without_matches)
create_thread(agent_without_paper)
create_thread(agent_without_tobacco)
agent_without_matches:
while true do
wait(agent_sem)
signal(paper)
signal(tobacco)
agent_without_paper:
while true do
wait(agent_sem)
signal(match)
signal(tobacco)
agent_without_tobacco:
while true do
wait(agent_sem)
signal(match)
signal(paper)
smoker_with_matches:
while true do
wait(paper)
wait(tobacco)
make_cigarette()
signal(agent_sem)
smoke()
smoker_with_paper:
while true do
wait(match)
wait(tobacco)
make_cigarette()
signal(agent_sem)
smoke()
smoker_with_tobacco:
while true do
wait(match)
wait(paper)
make_cigarette()
signal(agent_sem)
smoke()
1.3. Concurrencia compartida declarativa (OpenMP)
OpenMP es una tecnología desarrollada por varias empresas para implementar concurrencia de manera más declarativa y menos imperativa. Es una especificación para C y Fortran que varios compiladores implementan. Conviene tener a mano la guía de referencia al iniciar con esta tecnología. Para los ejemplos de esta sección cree una carpeta ejemplos/openmp
en su repositorio personal de control de versiones para el curso.
1.3.1. Hola mundo (región paralela, directivas)
El siguiente es un "Hola mundo" en OpenMP. Por elección de los estudiantes, se cambia el lenguaje de programación de C a C++.
Archivo | Descripción | Cambios |
---|---|---|
Dice "hola mundo" desde hilos secundarios en OpenMP. En esta tecnología el hilo principal sólo crea y espera por los hilos secundarios por tanto, no puede realizar otra acción mientras los hilos secundarios están en ejecución. |
||
Agrega la bandera |
Para compilar con GCC o un compilador compatible debe habilitarse OpenMP con la opción -fopenmp
. El Makefile
del ejemplo incluye esta bandera:
gcc -g -Wall -Wextra -fopenmp source.c -o executable
g++ -g -Wall -Wextra -fopenmp source.cpp -o executable
1.3.2. Hola mundo múltiple (funciones de biblioteca)
Archivo | Descripción | Cambios |
---|---|---|
Cada hilo secundario saluda diciendo su número en el equipo (team) y la cantidad de miembros en el equipo. |
||
Igual al ejemplo anterior. |
1.3.3. Repartir las iteraciones (omp parallel for
)
La directiva omp parallel
crea siempre una región paralela, que implica la creación y destrucción (join) de threads. La instrucción o bloque paralelizado es ejecutado por todos los _threads del equipo. La directiva omp parallel for
siempre debe estar seguida por un ciclo por contador (for
) estructurado (sin terminaciones abruptas como break
, continue
, y return
). Por ser una región paralela (por la palabra parallel
) también crea y destruye threads, pero a diferencia de omp parallel
, omp parallel for
reparte las iteraciones del ciclo entre los threads creados:
Archivo | Descripción | Cambios |
---|---|---|
La directiva |
||
Igual al ejemplo anterior. |
1.3.4. Reutilizar hilos (omp for
)
Si se tienen varias regiones parallel for
una tras otra que utilizan la misma cantidad de threads, es ineficiente crearlos y destruirlos cada vez que se ingresa y sale de estas secciones, de ahí la utilidad de la directiva for
.
Archivo | Descripción | Cambios |
---|---|---|
La directiva |
||
Igual al ejemplo anterior. |
1.3.5. Ordenamiento par-impar
1.3.6. Medición de duraciones con gprof
y perf
Los siguientes comandos resumen el uso de gprof
:
# Compile a modified executable that measures function call durations
gcc -g -Wall -Wextra -pg -no-pie source.c -o executable
# Run the executable as usual, it will generate binary file gmon.out
./executable args
# Generate a report from the gmon.out binary log
gprof ./executable gmon.out > report.txt
La herramienta perf
provee información menos detallada que gprof
, pero tiene la ventanja de que no es necesario modificar el ejecutable. Para obtener la duración se puede anteceder el comando con perf stat
de la forma:
perf stat ./executable args
1.3.7. Descomposición y mapeo (schedule
)
Archivo | Descripción | Cambios |
---|---|---|
Reparte iteraciones entre threads y guarda en arreglos qué thread hizo cada iteración. |
||
Makefile para compilar el código fuente. |
1.3.8. Reducciones (reduction
)
Archivo | Descripción | Cambios |
---|---|---|
Calcula el promedio de números leídos de la entrada estándar y almacenados en un arreglo dinámico. Usa varios threads para calcular las sumas. Usa reducciones para agregar las sumas parciales de cada thread a la suma total. |
||
Makefile para compilar el código fuente. |
2. Concurrencia distribuida
El paradigma de programación distribuido permite crear programas escalables que aprovechan máquinas que pueden correr procesos y pueden comunicarse a través de redes de computadoras. Se distingue de la distribución compartida en que los hilos pueden acceder a la misma memoria y comparten el mismo reloj. En la concurrencia distribuida, los procesos tienen cada uno su propia memoria exclusiva y los relojes pueden ser distintos, por lo que tienen que comunicarse a través de paso de mensajes.
Existen dos variantes de la distribución. En la distribución heterogénea, los ambientes en el que corre el programa son distintos: diferente hardware, sistema operativo, huso horario, etc., lo que forma una malla de computadoras. En la distribución homogénea, el ambiente (tanto el hardware como el software) debe ser idéntico para todos los procesos del programa, lo que se llama un clúster de computadoras. La distribución homogénea logra conseguir los mayores niveles de paralelismo.
2.1. Distribución homogénea: MPI
Message Passing Interface (MPI) es una tecnología de distribución homogénea creado por un grupo de académicos con el fin de facilitar el parelismo de aplicaciones científicas, que se convirtió en un estándar de facto. Existen otras tecnologías como Charm++ de más alto nivel.
Cree una carpeta ejemplos/mpi/
en su repositorio de control de versiones.
2.1.1. Hola mundo (proceso)
El siguiente es un "Hola mundo" en MPI:
Archivo | Descripción | Cambios |
---|---|---|
Dice "hola mundo" desde uno o varios procesos en una o varias máquinas. |
||
Para compilar programas con MPI se requiere instalar una implementación de MPI como |
||
Si se quiere que el programa corra en varios nodos de un clúster, se requiere un archivo que los liste. Este archivo está diseñado para mpich y el clúster arenal de la ECCI. Después de generado el ejecutable se puede invocar con |
2.1.2. Distribución híbrida (proceso-hilo)
Archivo | Descripción | Cambios |
---|---|---|
Dice "hola mundo" desde el hilo principal e hilos secundarios en varios procesos. |
||
Agrega las banderas para habilitar OpenMP. La bandera |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
Archivo | Descripción | Cambios |
---|---|---|
Reparte un rango de valores dado por argumento de línea de comandos entre procesos e hilos. |
||
Makefile para compilar el código fuente |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
2.1.3. Comunicación punto a punto (send-receive)
La comunicación punto a punto envía mensajes de un proceso a otro. MPI provee una familia de funciones para enviar y recibir mensajes. Las dos más básicas son: MPI_Send y MPI_Recv.
Archivo | Descripción | Cambios |
---|---|---|
Todos los procesos envían mensajes de hola al proceso 0 quien los recibe e imprime en orden de rank en la salida estándar, y no en el orden en que fueron recibidos. |
||
Un makefile genérico para compilar con MPI. |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
Archivo | Descripción | Cambios |
---|---|---|
Todos los procesos envían mensajes de hola al proceso 0 quien los recibe e imprime en orden de rank en la salida estándar, y no en el orden en que fueron recibidos. |
||
Un makefile genérico para compilar con MPI. |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
Archivo | Descripción | Cambios |
---|---|---|
Todos los procesos envían mensajes de hola al proceso 0 quien los recibe e imprime en el orden en que los recibió, y no en el orden de _rank. Los cambios a la derecha son respecto a la versión que imprime los mensajes en orden. |
||
Un makefile genérico para compilar con MPI. |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
2.1.4. Lectura distribuida y tiempo de pared
MPI permite que sólo el proceso 0 lea de la entrada estándar. Más específicamente, la entrada la lee el comando mpiexec
, la captura y la envía al proceso 0. Los demás procesos no reciben la entrada, por tanto si intentan leer, se quedarán bloqueados perennemente. Si un proceso debe leer de la entrada estándar, el proceso 0 tendrá que usar comunicación para enviarla a los demás procesos.
Archivo | Descripción | Cambios |
---|---|---|
Reparte un rango de valores dado leído de la entrada estándar entre procesos e hilos. |
||
Makefile para compilar el código fuente |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
Para calcular la duración en segundos del tiempo en la pared use la función MPI_Wtime.
Archivo | Descripción | Cambios |
---|---|---|
Mide el tiempo de ejecución repartiendo un rango de valores, sea dado por argumentos o leído de la entrada estándar entre procesos e hilos. Esperablemente lo segundo requiere más tiempo. |
||
Makefile para compilar el código fuente |
||
Rango para leer de la entrada estándar y reducir el tiempo que tarda un humano en digitar. Se puede invocar de la forma |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
2.1.5. Comunicación colectiva: broadcast
Archivo | Descripción | Cambios |
---|---|---|
El proceso 0 lee el rango de la entrada estándar (puesto que es el único en MPI que puede hacerlo), si no se provee en los argumentos de línea de comandos. Luego envía copias del rango a los demás procesos con la subrutina MPI_Bcast. Esta subrutina es típicamente más eficiente que ejecutar un ciclo de envíos ( |
||
Makefile para compilar el código fuente |
||
Rango para leer de la entrada estándar y reducir el tiempo que tarda un humano en digitar. Se puede invocar de la forma |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
2.1.6. Comunicación colectiva: reduce
Archivo | Descripción | Cambios |
---|---|---|
Todos los procesos escogen un número de la suerte al azar. Note que se usa el número de proceso en la semilla para generar números pseudoaletorios, de lo contrario, es probable que todos los procesos escojan el mismo número de la suerte.. |
||
Un makefile genérico para compilar con MPI. |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
2.1.7. Comunicación colectiva: all-reduce
Archivo | Descripción | Cambios |
---|---|---|
Cada proceso escoge un número de la suerte y lo envía a todos los demás. Cada proceso recibe el mínimo, la suma y el máximo de los números escogidos. Cada proceso calcula el promedio y compara su número de la suerte contra los estadísticos anteriores. |
||
Un makefile genérico para compilar con MPI. |
||
Nodos mpich del clúster arenal de la ECCI, para poder invocar con |
2.2. Distribución heterogénea: aceleración gráfica
Cree una carpeta ejemplos/gpu
en su repositorio personal.
2.2.1. Transmisión de calor
Archivo | Descripción | Cambios |
---|---|---|
Hoja de cálculo para anotar duraciones de ejecución de las actividades siguientes. |
||
Algunos casos de prueba binarios. |
Archivo | Descripción | Cambios |
---|---|---|
(Versión serial de libro/documentación) |
||
Recibe dos argumentos: un archivo binario que contiene una matrix de flotantes de doble precisión, y un epsilon. Simula transmisión de calor desde el borde de la matriz sobre toda la superficie, hasta que el calor se equilibre. Es decir, hasta que ninguna diferencia supere el epsilon. |
||
Un makefile para compilar con GCC. |
||
Un makefile para compilar con PGI (antiguamente The Portland Group). Establece la variable PATH dependiente del laboratorio 102-IF. Para usarlo use la opción |